#![cfg_attr(not(test), allow(unused_imports))]
use crate::chain::chaininterface::{FundingPurpose, TransactionType, FEERATE_FLOOR_SATS_PER_KW};
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::OutPoint;
use crate::chain::ChannelMonitorUpdateStatus;
use crate::events::{
ClosureReason, Event, FundingInfo, HTLCHandlingFailureType, NegotiationFailureReason,
};
use crate::ln::chan_utils;
use crate::ln::channel::{
ANCHOR_OUTPUT_VALUE_SATOSHI, CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY,
DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE,
MIN_CHANNEL_VALUE_SATOSHIS,
};
use crate::ln::channel_state::{SpliceCandidateDetails, SpliceCandidateStatus, SpliceDetails};
use crate::ln::channelmanager::{provided_init_features, PaymentId, BREAKDOWN_TIMEOUT};
use crate::ln::functional_test_utils::*;
use crate::ln::funding::{FundingContribution, FundingContributionError, FundingTemplate};
use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, MessageSendEvent};
use crate::ln::outbound_payment::RecipientOnionFields;
use crate::ln::types::ChannelId;
use crate::routing::router::{PaymentParameters, RouteParameters};
use crate::types::features::ChannelTypeFeatures;
use crate::types::string::UntrustedString;
use crate::util::config::UserConfig;
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
use crate::util::test_channel_signer::SignerOp;
use crate::util::wallet_utils::{
CoinSelection, CoinSelectionSourceSync, ConfirmedUtxo, Input, WalletSourceSync, WalletSync,
};
use crate::sync::Arc;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use bitcoin::transaction::Version;
use bitcoin::SignedAmount;
use bitcoin::{
Amount, FeeRate, OutPoint as BitcoinOutPoint, Psbt, ScriptBuf, Transaction, TxOut, Txid,
WPubkeyHash, WScriptHash,
};
#[test]
fn test_splicing_not_supported_api_error() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut features = provided_init_features(&test_default_channel_config());
features.clear_splicing();
*node_cfgs[0].override_init_features.borrow_mut() = Some(features);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 0, 1);
let res = nodes[1].node.splice_channel(&channel_id, &node_id_0);
match res {
Err(APIError::ChannelUnavailable { err }) => {
assert!(err.contains("Peer does not support splicing"))
},
_ => panic!("Wrong error {:?}", res.err().unwrap()),
}
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let mut features = nodes[0].node.init_features();
features.set_splicing_optional();
features.clear_quiescence();
*nodes[0].override_init_features.borrow_mut() = Some(features);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
let res = nodes[1].node.splice_channel(&channel_id, &node_id_0);
match res {
Err(APIError::ChannelUnavailable { err }) => {
assert!(err.contains("Peer does not support quiescence, a splicing prerequisite"))
},
_ => panic!("Wrong error {:?}", res.err().unwrap()),
}
}
#[test]
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);
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let splice_in_value = Amount::from_sat(20_000);
let extra_splice_funding_input = splice_in_value - Amount::ONE_SAT;
provide_utxo_reserves(&nodes, 1, extra_splice_funding_input);
let feerate = FeeRate::from_sat_per_kwu(1024);
let funding_template =
nodes[0].node.splice_channel(&channel_id, &nodes[1].node.get_our_node_id()).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(funding_template
.splice_in_sync(splice_in_value, feerate, FeeRate::MAX, &wallet)
.is_err());
}
#[cfg(test)]
struct TightBudgetWallet {
utxo_value: Amount,
change_value: Amount,
}
#[cfg(test)]
impl CoinSelectionSourceSync for TightBudgetWallet {
fn select_confirmed_utxos(
&self, _claim_id: Option<crate::chain::ClaimId>, _must_spend: Vec<Input>,
_must_pay_to: &[TxOut], _target_feerate_sat_per_1000_weight: u32, _max_tx_weight: u64,
) -> Result<CoinSelection, ()> {
let prevout = TxOut {
value: self.utxo_value,
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
};
let prevtx = Transaction {
input: vec![],
output: vec![prevout],
version: Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
};
let utxo = ConfirmedUtxo::new_p2wpkh(prevtx, 0).unwrap();
let change_output = TxOut {
value: self.change_value,
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
};
Ok(CoinSelection { confirmed_utxos: vec![utxo], change_output: Some(change_output) })
}
fn sign_psbt(&self, _psbt: Psbt) -> Result<Transaction, ()> {
unreachable!("should not reach signing")
}
}
#[cfg(test)]
fn config_with_min_funding_satoshis(min_funding_satoshis: u64) -> UserConfig {
let mut config = test_default_channel_config();
config.channel_handshake_limits.min_funding_satoshis = min_funding_satoshis;
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
config
}
#[cfg(test)]
fn assert_min_funding_error<'a, 'b, 'c>(
node: &Node<'a, 'b, 'c>, recipient: PublicKey, min_funding_satoshis: u64,
) {
let msg = get_event_msg!(node, MessageSendEvent::SendTxAbort, recipient);
let data = tx_abort_data(&msg);
assert!(
data.contains(&format!("configured min_funding_satoshis {min_funding_satoshis}")),
"unexpected tx_abort: {}",
data
);
}
#[cfg(test)]
fn tx_abort_data(msg: &msgs::TxAbort) -> String {
String::from_utf8(msg.data.clone()).expect("tx_abort data should be valid UTF-8")
}
pub fn negotiate_splice_tx<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
funding_contribution: FundingContribution,
) {
let new_funding_script = complete_splice_handshake(initiator, acceptor);
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
funding_contribution,
new_funding_script,
);
}
pub fn initiate_splice_in<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
value_added: Amount,
) -> FundingContribution {
do_initiate_splice_in(initiator, acceptor, channel_id, value_added)
}
pub fn do_initiate_splice_in<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
value_added: Amount,
) -> FundingContribution {
let node_id_acceptor = acceptor.node.get_our_node_id();
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = initiator.node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
let feerate = funding_template.min_rbf_feerate().unwrap_or(floor_feerate);
let wallet = WalletSync::new(Arc::clone(&initiator.wallet_source), initiator.logger);
let funding_contribution =
funding_template.splice_in_sync(value_added, feerate, FeeRate::MAX, &wallet).unwrap();
initiator
.node
.funding_contributed(&channel_id, &node_id_acceptor, funding_contribution.clone(), None)
.unwrap();
funding_contribution
}
pub fn do_initiate_rbf_splice_in<'a, 'b, 'c, 'd>(
node: &'a Node<'b, 'c, 'd>, counterparty: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
feerate: FeeRate,
) -> FundingContribution {
let node_id_counterparty = counterparty.node.get_our_node_id();
let funding_template = node.node.splice_channel(&channel_id, &node_id_counterparty).unwrap();
let funding_contribution =
funding_template.with_prior_contribution(feerate, FeeRate::MAX).build().unwrap();
node.node
.funding_contributed(&channel_id, &node_id_counterparty, funding_contribution.clone(), None)
.unwrap();
funding_contribution
}
pub fn do_initiate_rbf_splice_in_and_out<'a, 'b, 'c, 'd>(
node: &'a Node<'b, 'c, 'd>, counterparty: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
outputs: Vec<TxOut>, feerate: FeeRate,
) -> FundingContribution {
let node_id_counterparty = counterparty.node.get_our_node_id();
let funding_template = node.node.splice_channel(&channel_id, &node_id_counterparty).unwrap();
let funding_contribution = funding_template
.with_prior_contribution(feerate, FeeRate::MAX)
.add_outputs(outputs)
.build()
.unwrap();
node.node
.funding_contributed(&channel_id, &node_id_counterparty, funding_contribution.clone(), None)
.unwrap();
funding_contribution
}
pub fn do_initiate_splice_in_at_feerate<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
value_added: Amount, feerate: FeeRate,
) -> FundingContribution {
let node_id_acceptor = acceptor.node.get_our_node_id();
let funding_template = initiator.node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
let wallet = WalletSync::new(Arc::clone(&initiator.wallet_source), initiator.logger);
let funding_contribution =
funding_template.splice_in_sync(value_added, feerate, FeeRate::MAX, &wallet).unwrap();
initiator
.node
.funding_contributed(&channel_id, &node_id_acceptor, funding_contribution.clone(), None)
.unwrap();
funding_contribution
}
pub fn initiate_splice_out<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
outputs: Vec<TxOut>,
) -> Result<FundingContribution, APIError> {
let node_id_acceptor = acceptor.node.get_our_node_id();
let funding_contribution =
build_splice_out_contribution(initiator, acceptor, channel_id, outputs).unwrap();
match initiator.node.funding_contributed(
&channel_id,
&node_id_acceptor,
funding_contribution.clone(),
None,
) {
Ok(()) => Ok(funding_contribution),
Err(e) => {
expect_splice_failed_events(
initiator,
&channel_id,
funding_contribution,
NegotiationFailureReason::ContributionInvalid,
);
Err(e)
},
}
}
pub fn build_splice_out_contribution<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
outputs: Vec<TxOut>,
) -> Result<FundingContribution, FundingContributionError> {
let node_id_acceptor = acceptor.node.get_our_node_id();
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = initiator.node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
let feerate = funding_template.min_rbf_feerate().unwrap_or(floor_feerate);
funding_template.splice_out(outputs, feerate, FeeRate::MAX)
}
pub fn initiate_splice_in_and_out<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
value_added: Amount, outputs: Vec<TxOut>,
) -> FundingContribution {
do_initiate_splice_in_and_out(initiator, acceptor, channel_id, value_added, outputs)
}
pub fn do_initiate_splice_in_and_out<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
value_added: Amount, outputs: Vec<TxOut>,
) -> FundingContribution {
let node_id_acceptor = acceptor.node.get_our_node_id();
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = initiator.node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
let feerate = funding_template.min_rbf_feerate().unwrap_or(floor_feerate);
let wallet = WalletSync::new(Arc::clone(&initiator.wallet_source), initiator.logger);
let funding_contribution = funding_template
.without_prior_contribution(feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(value_added)
.unwrap()
.add_outputs(outputs)
.build()
.unwrap();
initiator
.node
.funding_contributed(&channel_id, &node_id_acceptor, funding_contribution.clone(), None)
.unwrap();
funding_contribution
}
pub fn complete_splice_handshake<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>,
) -> ScriptBuf {
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.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();
new_funding_script
}
pub fn complete_rbf_handshake<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>,
) -> msgs::TxAckRbf {
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.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 tx_init_rbf = get_event_msg!(initiator, MessageSendEvent::SendTxInitRbf, node_id_acceptor);
acceptor.node.handle_tx_init_rbf(node_id_initiator, &tx_init_rbf);
let tx_ack_rbf = get_event_msg!(acceptor, MessageSendEvent::SendTxAckRbf, node_id_initiator);
initiator.node.handle_tx_ack_rbf(node_id_acceptor, &tx_ack_rbf);
tx_ack_rbf
}
pub 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: FundingContribution, new_funding_script: ScriptBuf,
) {
complete_interactive_funding_negotiation_for_both(
initiator,
acceptor,
channel_id,
initiator_contribution,
None,
0,
new_funding_script,
);
}
pub fn complete_interactive_funding_negotiation_for_both<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
initiator_contribution: FundingContribution,
acceptor_contribution: Option<FundingContribution>, acceptor_funding_satoshis: i64,
new_funding_script: ScriptBuf,
) {
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let (funding_outpoint, channel_value_satoshis) = 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(), channel.channel_value_satoshis))
.unwrap();
let new_channel_value = Amount::from_sat(
channel_value_satoshis
.checked_add_signed(initiator_contribution.net_value().to_sat())
.unwrap()
.checked_add_signed(acceptor_funding_satoshis)
.unwrap(),
);
let (initiator_funding_tx_inputs, mut expected_initiator_outputs) =
initiator_contribution.into_tx_parts();
let mut expected_initiator_inputs = initiator_funding_tx_inputs
.iter()
.map(|input| input.utxo.outpoint)
.chain(core::iter::once(funding_outpoint.into_bitcoin_outpoint()))
.collect::<Vec<_>>();
expected_initiator_outputs
.push(TxOut { script_pubkey: new_funding_script, value: new_channel_value });
let (mut expected_acceptor_inputs, mut expected_acceptor_scripts) =
if let Some(acceptor_contribution) = acceptor_contribution {
let (acceptor_inputs, acceptor_outputs) = acceptor_contribution.into_tx_parts();
let expected_acceptor_inputs =
acceptor_inputs.iter().map(|input| input.utxo.outpoint).collect::<Vec<_>>();
let expected_acceptor_scripts =
acceptor_outputs.into_iter().map(|output| output.script_pubkey).collect::<Vec<_>>();
(expected_acceptor_inputs, expected_acceptor_scripts)
} else {
(Vec::new(), Vec::new())
};
let mut initiator_sent_tx_complete;
let mut acceptor_sent_tx_complete = false;
loop {
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
match &msg_events[0] {
MessageSendEvent::SendTxAddInput { msg, .. } => {
let input_prevout = BitcoinOutPoint {
txid: msg
.prevtx
.as_ref()
.map(|prevtx| prevtx.compute_txid())
.or(msg.shared_input_txid)
.unwrap(),
vout: msg.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, msg);
initiator_sent_tx_complete = false;
},
MessageSendEvent::SendTxAddOutput { msg, .. } => {
expected_initiator_outputs.remove(
expected_initiator_outputs
.iter()
.position(|output| {
*output.script_pubkey == msg.script && output.value.to_sat() == msg.sats
})
.unwrap(),
);
acceptor.node.handle_tx_add_output(node_id_initiator, msg);
initiator_sent_tx_complete = false;
},
MessageSendEvent::SendTxComplete { msg, .. } => {
acceptor.node.handle_tx_complete(node_id_initiator, msg);
initiator_sent_tx_complete = true;
if acceptor_sent_tx_complete {
break;
}
},
_ => panic!("Unexpected message event: {:?}", msg_events[0]),
}
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
match &msg_events[0] {
MessageSendEvent::SendTxAddInput { msg, .. } => {
let input_prevout = BitcoinOutPoint {
txid: msg
.prevtx
.as_ref()
.map(|prevtx| prevtx.compute_txid())
.or(msg.shared_input_txid)
.unwrap(),
vout: msg.prevtx_out,
};
expected_acceptor_inputs.remove(
expected_acceptor_inputs
.iter()
.position(|input| *input == input_prevout)
.unwrap(),
);
initiator.node.handle_tx_add_input(node_id_acceptor, msg);
acceptor_sent_tx_complete = false;
},
MessageSendEvent::SendTxAddOutput { msg, .. } => {
expected_acceptor_scripts.remove(
expected_acceptor_scripts
.iter()
.position(|script| *script == msg.script)
.unwrap(),
);
initiator.node.handle_tx_add_output(node_id_acceptor, msg);
acceptor_sent_tx_complete = false;
},
MessageSendEvent::SendTxComplete { msg, .. } => {
initiator.node.handle_tx_complete(node_id_acceptor, msg);
acceptor_sent_tx_complete = true;
if initiator_sent_tx_complete {
break;
}
},
_ => panic!("Unexpected message event: {:?}", msg_events[0]),
}
}
assert!(expected_initiator_inputs.is_empty(), "Not all initiator inputs were sent");
assert!(expected_initiator_outputs.is_empty(), "Not all initiator outputs were sent");
assert!(expected_acceptor_inputs.is_empty(), "Not all acceptor inputs were sent");
assert!(expected_acceptor_scripts.is_empty(), "Not all acceptor outputs were sent");
}
pub struct SignInteractiveFundingTxArgs<'a, 'b, 'c, 'd> {
initiator: &'a Node<'b, 'c, 'd>,
acceptor: &'a Node<'b, 'c, 'd>,
is_0conf: bool,
acceptor_has_contribution: bool,
expected_replaced_txid: Option<Txid>,
unconfirmed_funding_txid: Option<Txid>,
}
impl<'a, 'b, 'c, 'd> SignInteractiveFundingTxArgs<'a, 'b, 'c, 'd> {
pub fn new(initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>) -> Self {
Self {
initiator,
acceptor,
is_0conf: false,
acceptor_has_contribution: false,
expected_replaced_txid: None,
unconfirmed_funding_txid: None,
}
}
pub fn zero_conf(mut self) -> Self {
self.is_0conf = true;
self
}
pub fn with_acceptor_contribution(mut self) -> Self {
self.acceptor_has_contribution = true;
self
}
pub fn replacing(mut self, prior_txid: Txid) -> Self {
self.expected_replaced_txid = Some(prior_txid);
self
}
pub fn with_unconfirmed_funding(mut self, unconfirmed_funding_txid: Txid) -> Self {
self.unconfirmed_funding_txid = Some(unconfirmed_funding_txid);
self
}
}
pub fn sign_interactive_funding_tx<'a, 'b, 'c, 'd>(
args: SignInteractiveFundingTxArgs<'a, 'b, 'c, 'd>,
) -> (Transaction, Option<(msgs::SpliceLocked, PublicKey)>) {
let SignInteractiveFundingTxArgs {
initiator,
acceptor,
is_0conf,
acceptor_has_contribution,
expected_replaced_txid,
unconfirmed_funding_txid,
} = args;
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());
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
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();
} else {
panic!();
}
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
let initial_commit_sig_for_acceptor =
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
updates.commitment_signed[0].clone()
} else {
panic!();
};
acceptor.node.handle_commitment_signed(node_id_initiator, &initial_commit_sig_for_acceptor);
if acceptor_has_contribution {
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
let event = get_event!(acceptor, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
let partially_signed_tx = acceptor.wallet_source.sign_tx(unsigned_transaction).unwrap();
acceptor
.node
.funding_transaction_signed(&channel_id, &counterparty_node_id, partially_signed_tx)
.unwrap();
} else {
panic!();
}
}
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
let commitment_signed = &updates.commitment_signed[0];
initiator.node.handle_commitment_signed(node_id_acceptor, commitment_signed);
} else {
panic!();
}
if let MessageSendEvent::SendTxSignatures { ref msg, .. } = &msg_events[1] {
initiator.node.handle_tx_signatures(node_id_acceptor, msg);
} else {
panic!();
}
let mut msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if is_0conf { 2 } else { 1 }, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { ref msg, .. } = &msg_events[0] {
acceptor.node.handle_tx_signatures(node_id_initiator, msg);
} else {
panic!();
}
let splice_locked = if is_0conf {
if let MessageSendEvent::SendSpliceLocked { msg, .. } = msg_events.remove(1) {
Some((msg, node_id_acceptor))
} else {
panic!();
}
} else {
None
};
check_added_monitors(&initiator, 1);
check_added_monitors(&acceptor, 1);
let tx = {
let mut initiator_txn = initiator.tx_broadcaster.txn_broadcast_with_types();
if let Some(unconfirmed_funding_txid) = unconfirmed_funding_txid {
assert_eq!(initiator_txn.len(), 2);
let pos = initiator_txn
.iter()
.position(|(tx, tx_type)| {
tx.compute_txid() == unconfirmed_funding_txid
&& matches!(tx_type, TransactionType::Funding { .. })
})
.expect("the unconfirmed funding should be (re-)broadcast");
initiator_txn.remove(pos);
}
assert_eq!(initiator_txn.len(), 1);
let mut acceptor_txn = acceptor.tx_broadcaster.txn_broadcast_with_types();
assert_eq!(acceptor_txn.len(), 1);
assert_eq!(initiator_txn[0].0, acceptor_txn[0].0);
let (tx, initiator_tx_type) = initiator_txn.remove(0);
let (_, acceptor_tx_type) = acceptor_txn.remove(0);
let assert_broadcast =
|label: &str, tx_type: &TransactionType, contribution_expected: bool| {
let candidates = match tx_type {
TransactionType::InteractiveFunding { candidates } => candidates,
other => panic!("Expected TransactionType::InteractiveFunding, got {other:?}"),
};
let last = candidates.last().expect("at least one candidate");
assert_eq!(last.txid, tx.compute_txid(), "{label} last candidate txid mismatch");
let last_channel = last.channels.first().expect("at least one channel");
assert!(matches!(last_channel.purpose, FundingPurpose::Splice));
assert_eq!(
last_channel.contribution.is_some(),
contribution_expected,
"{label} contribution presence mismatch",
);
let prior_txid = candidates.len().checked_sub(2).map(|i| candidates[i].txid);
assert_eq!(prior_txid, expected_replaced_txid, "{label} replaced_txid mismatch");
};
assert_broadcast("initiator", &initiator_tx_type, true);
assert_broadcast("acceptor", &acceptor_tx_type, acceptor_has_contribution);
tx
};
(tx, splice_locked)
}
pub fn splice_channel<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
funding_contribution: FundingContribution,
) -> (Transaction, ScriptBuf) {
let node_id_acceptor = acceptor.node.get_our_node_id();
let new_funding_script = complete_splice_handshake(initiator, acceptor);
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
funding_contribution,
new_funding_script.clone(),
);
let (splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(initiator, acceptor));
assert!(splice_locked.is_none());
expect_splice_pending_event(initiator, &node_id_acceptor);
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
(splice_tx, new_funding_script)
}
pub struct SpliceLockedResult {
pub stfu: Option<MessageSendEvent>,
pub node_a_discarded: Vec<(Vec<bitcoin::OutPoint>, Vec<ScriptBuf>)>,
pub node_b_discarded: Vec<(Vec<bitcoin::OutPoint>, Vec<ScriptBuf>)>,
}
pub fn lock_splice_after_blocks<'a, 'b, 'c, 'd>(
node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, num_blocks: u32,
) -> SpliceLockedResult {
connect_blocks(node_a, num_blocks);
connect_blocks(node_b, num_blocks);
let node_id_b = node_b.node.get_our_node_id();
let splice_locked_for_node_b =
get_event_msg!(node_a, MessageSendEvent::SendSpliceLocked, node_id_b);
lock_splice(node_a, node_b, &splice_locked_for_node_b, false, &[])
}
pub fn lock_splice<'a, 'b, 'c, 'd>(
node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>,
splice_locked_for_node_b: &msgs::SpliceLocked, is_0conf: bool, expected_discard_txids: &[Txid],
) -> SpliceLockedResult {
let prev_funding_txid = node_a
.chain_monitor
.chain_monitor
.get_monitor(splice_locked_for_node_b.channel_id)
.map(|monitor| monitor.get_funding_txo().txid)
.unwrap();
complete_splice_locked_exchange(
node_a,
node_b,
splice_locked_for_node_b,
is_0conf,
expected_discard_txids,
prev_funding_txid,
)
}
fn complete_splice_locked_exchange<'a, 'b, 'c, 'd>(
node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>,
splice_locked_for_node_b: &msgs::SpliceLocked, is_0conf: bool, expected_discard_txids: &[Txid],
prev_funding_txid: Txid,
) -> SpliceLockedResult {
let node_id_a = node_a.node.get_our_node_id();
let node_id_b = node_b.node.get_our_node_id();
node_b.node.handle_splice_locked(node_id_a, splice_locked_for_node_b);
let mut msg_events = node_b.node.get_and_clear_pending_msg_events();
let node_b_stfu = msg_events
.last()
.filter(|event| matches!(event, MessageSendEvent::SendStfu { .. }))
.is_some()
.then(|| msg_events.pop().unwrap());
assert_eq!(msg_events.len(), if is_0conf { 1 } else { 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 !is_0conf {
if let MessageSendEvent::SendAnnouncementSignatures { msg, .. } = msg_events.remove(0) {
node_a.node.handle_announcement_signatures(node_id_b, &msg);
} else {
panic!();
}
}
let mut node_a_discarded = Vec::new();
let mut node_b_discarded = Vec::new();
for (idx, node) in [node_a, node_b].into_iter().enumerate() {
let events = node.node.get_and_clear_pending_events();
assert!(!events.is_empty(), "Expected at least ChannelReady, got {events:?}");
assert!(matches!(events[0], Event::ChannelReady { .. }));
let discarded = if idx == 0 { &mut node_a_discarded } else { &mut node_b_discarded };
for event in &events[1..] {
match event {
Event::DiscardFunding {
funding_info: FundingInfo::Contribution { inputs, outputs },
..
} => {
discarded.push((inputs.clone(), outputs.clone()));
},
other => panic!("Expected DiscardFunding with Contribution, got {:?}", other),
}
}
check_added_monitors(node, 1);
}
let mut node_a_stfu = None;
if !is_0conf {
let mut msg_events = node_a.node.get_and_clear_pending_msg_events();
node_a_stfu = msg_events
.iter()
.position(|event| matches!(event, MessageSendEvent::SendStfu { .. }))
.map(|i| msg_events.remove(i));
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 {
panic!();
}
if let MessageSendEvent::BroadcastChannelAnnouncement { .. } = msg_events.remove(0) {
} else {
panic!();
}
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!();
}
}
for node in [node_a, node_b] {
node.chain_source.remove_watched_by_txid(prev_funding_txid);
for txid in expected_discard_txids {
node.chain_source.remove_watched_by_txid(*txid);
}
}
SpliceLockedResult { stfu: node_a_stfu.or(node_b_stfu), node_a_discarded, node_b_discarded }
}
pub fn lock_rbf_splice_after_blocks<'a, 'b, 'c, 'd>(
node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, tx: &Transaction, num_blocks: u32,
expected_discard_txids: &[Txid],
) -> SpliceLockedResult {
mine_transaction(node_a, tx);
mine_transaction(node_b, tx);
connect_blocks(node_a, num_blocks);
connect_blocks(node_b, num_blocks);
let node_id_b = node_b.node.get_our_node_id();
let splice_locked_for_node_b =
get_event_msg!(node_a, MessageSendEvent::SendSpliceLocked, node_id_b);
lock_splice(node_a, node_b, &splice_locked_for_node_b, false, expected_discard_txids)
}
#[test]
fn test_splice_state_reset_on_disconnect() {
do_test_splice_state_reset_on_disconnect(false);
do_test_splice_state_reset_on_disconnect(true);
}
#[cfg(test)]
fn do_test_splice_state_reset_on_disconnect(reload: bool) {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let (
persister_0a,
persister_0b,
persister_0c,
persister_0d,
persister_1a,
persister_1b,
persister_1c,
persister_1d,
);
let (
chain_monitor_0a,
chain_monitor_0b,
chain_monitor_0c,
chain_monitor_0d,
chain_monitor_1a,
chain_monitor_1b,
chain_monitor_1c,
chain_monitor_1d,
);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let (node_0a, node_0b, node_0c, node_0d, node_1a, node_1b, node_1c, node_1d);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs.clone()).unwrap();
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
&nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0a,
chain_monitor_0a,
node_0a
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1a,
chain_monitor_1a,
node_1a
);
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs.clone()).unwrap();
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let tx_add_input = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddInput, node_id_1);
nodes[1].node.handle_tx_add_input(node_id_0, &tx_add_input);
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
&nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0b,
chain_monitor_0b,
node_0b
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1b,
chain_monitor_1b,
node_1b
);
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs.clone()).unwrap();
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let tx_add_input = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddInput, node_id_1);
nodes[1].node.handle_tx_add_input(node_id_0, &tx_add_input);
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
let tx_add_output = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddOutput, node_id_1);
nodes[1].node.handle_tx_add_output(node_id_0, &tx_add_output);
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
let tx_add_output = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddOutput, node_id_1);
nodes[1].node.handle_tx_add_output(node_id_0, &tx_add_output);
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
if let MessageSendEvent::SendTxComplete { .. } = &msg_events[0] {
} else {
panic!("Unexpected event");
}
let _event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
&nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0c,
chain_monitor_0c,
node_0c
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1c,
chain_monitor_1c,
node_1c
);
let _ = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, false);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_args.send_tx_abort = (true, false);
reconnect_nodes(reconnect_args);
let tx_abort = get_event_msg!(nodes[0], MessageSendEvent::SendTxAbort, node_id_1);
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort);
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::CounterpartyAborted {
msg: UntrustedString(
"Signing was not completed for this funding transaction; it may be forgotten."
.to_string(),
),
},
);
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
&nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0d,
chain_monitor_0d,
node_0d
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1d,
chain_monitor_1d,
node_1d
);
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
}
#[test]
fn test_reload_resets_splice_negotiation_without_dropping_candidates() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let (persister_0, chain_monitor_0);
let node_0;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution.clone());
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.min_rbf_feerate(), Some(rbf_feerate));
assert!(funding_template.prior_contribution().is_some());
let rbf_contribution =
funding_template.with_prior_contribution(rbf_feerate, FeeRate::MAX).build().unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, rbf_contribution, None).unwrap();
complete_rbf_handshake(&nodes[0], &nodes[1]);
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0,
chain_monitor_0,
node_0
);
let _ = get_event!(&nodes[0], Event::SpliceNegotiationFailed);
let details = nodes[0]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 1);
assert!(matches!(details.candidates[0].status, SpliceCandidateStatus::Negotiated { .. }));
assert_eq!(details.candidates[0].contribution, Some(funding_contribution.clone()));
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.min_rbf_feerate(), Some(rbf_feerate));
assert_eq!(funding_template.prior_contribution().unwrap(), &funding_contribution);
}
#[test]
fn test_config_reject_inbound_splices() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut config = test_default_channel_config();
config.reject_inbound_splices = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs.clone()).unwrap();
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
if let MessageSendEvent::HandleError { action, .. } = &msg_events[0] {
assert!(matches!(action, msgs::ErrorAction::DisconnectPeerWithWarning { .. }));
} else {
panic!("Expected MessageSendEvent::HandleError");
}
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
let funding_contribution =
initiate_splice_out(&nodes[1], &nodes[0], channel_id, outputs).unwrap();
let _ = splice_channel(&nodes[1], &nodes[0], channel_id, funding_contribution);
}
#[test]
fn test_splice_in() {
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
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 added_value = Amount::from_sat(initial_channel_value_sat * 2);
let utxo_value = added_value * 3 / 4;
let fees = Amount::from_sat(322);
provide_utxo_reserves(&nodes, 2, utxo_value);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let expected_change = utxo_value * 2 - added_value - fees;
assert_eq!(
splice_tx
.output
.iter()
.find(|txout| txout.script_pubkey != new_funding_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 < initial_channel_value_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 > initial_channel_value_sat);
let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
}
#[test]
fn test_min_funding_satoshis_allows_splice_init_with_positive_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
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);
nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let added_value = Amount::from_sat(10_000);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let _funding_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
assert!(splice_init.funding_contribution_satoshis > 0);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let _splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
}
#[test]
fn test_min_funding_satoshis_rejects_splice_init_with_negative_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
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);
nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let outputs = vec![TxOut {
value: Amount::from_sat(10_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let _funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
assert!(splice_init.funding_contribution_satoshis < 0);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
assert_min_funding_error(&nodes[1], node_id_0, min_funding_satoshis);
}
#[test]
fn test_min_funding_satoshis_allows_outbound_splice_ack_with_negative_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
let initial_channel_value_sat = 100_000;
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
0,
1,
initial_channel_value_sat,
50_000_000,
);
nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let added_value = Amount::from_sat(10_000);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let _node_0_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let outputs = vec![TxOut {
value: Amount::from_sat(10_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
let _node_1_contribution =
initiate_splice_out(&nodes[1], &nodes[0], channel_id, outputs).unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert!(splice_ack.funding_contribution_satoshis < 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert!(!msg_events.is_empty(), "{msg_events:?}");
assert!(
!msg_events.iter().any(|event| matches!(event, MessageSendEvent::HandleError { .. })),
"{msg_events:?}"
);
}
#[test]
fn test_min_funding_satoshis_rejects_splice_ack_with_negative_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
let initial_channel_value_sat = 100_000;
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
1,
0,
initial_channel_value_sat,
50_000_000,
);
nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let added_value = Amount::from_sat(10_000);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let _node_0_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let outputs = vec![TxOut {
value: Amount::from_sat(10_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
let _node_1_contribution =
initiate_splice_out(&nodes[1], &nodes[0], channel_id, outputs).unwrap();
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
assert!(!stfu_ack.initiator);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert!(splice_ack.funding_contribution_satoshis < 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
assert_min_funding_error(&nodes[0], node_id_1, min_funding_satoshis);
}
#[test]
fn test_min_funding_satoshis_rejects_tx_init_rbf_with_negative_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
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);
nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let added_value = Amount::from_sat(10_000);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let first_contribution = initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let (_first_splice_tx, _) =
splice_channel(&nodes[1], &nodes[0], channel_id, first_contribution);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let outputs = vec![TxOut {
value: Amount::from_sat(10_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let rbf_contribution = funding_template.splice_out(outputs, rbf_feerate, FeeRate::MAX).unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, rbf_contribution, None).unwrap();
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert!(tx_init_rbf.funding_output_contribution.unwrap() < 0);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
assert_min_funding_error(&nodes[1], node_id_0, min_funding_satoshis);
}
#[test]
fn test_min_funding_satoshis_rejects_tx_ack_rbf_with_negative_counterparty_contribution() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let min_funding_satoshis = 150_000;
let initial_channel_value_sat = 100_000;
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
1,
0,
initial_channel_value_sat,
50_000_000,
);
nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis));
let added_value = Amount::from_sat(10_000);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let first_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_first_splice_tx, _) =
splice_channel(&nodes[0], &nodes[1], channel_id, first_contribution);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let rbf_feerate = funding_template_0.min_rbf_feerate().unwrap();
let node_0_contribution =
funding_template_0.with_prior_contribution(rbf_feerate, FeeRate::MAX).build().unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, node_0_contribution, None).unwrap();
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let outputs = vec![TxOut {
value: Amount::from_sat(10_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let node_1_contribution =
funding_template_1.splice_out(outputs, rbf_feerate, FeeRate::MAX).unwrap();
nodes[1].node.funding_contributed(&channel_id, &node_id_0, node_1_contribution, None).unwrap();
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
assert!(!stfu_ack.initiator);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
assert!(tx_ack_rbf.funding_output_contribution.unwrap() < 0);
nodes[0].node.handle_tx_ack_rbf(node_id_1, &tx_ack_rbf);
assert_min_funding_error(&nodes[0], node_id_1, min_funding_satoshis);
}
#[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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
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 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_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], 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);
}
#[test]
fn test_splice_in_and_out_funds_outputs_from_inputs() {
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
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 value_added = Amount::from_sat(20_000);
let utxo_value = Amount::from_sat(50_000);
let outputs = vec![
TxOut {
value: Amount::from_sat(20_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
},
TxOut {
value: Amount::from_sat(20_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
},
];
provide_utxo_reserves(&nodes, 2, utxo_value);
let funding_contribution =
initiate_splice_in_and_out(&nodes[0], &nodes[1], channel_id, value_added, outputs);
let fees = Amount::from_sat(385);
let total_output_value: Amount =
funding_contribution.outputs().iter().map(|output| output.value).sum();
let expected_change = utxo_value * 2 - value_added - total_output_value - fees;
assert_eq!(funding_contribution.change_output().unwrap().value, expected_change);
assert!(funding_contribution.net_value() >= value_added.to_signed().unwrap());
let (splice_tx, _) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution.clone());
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
let channel = &nodes[0].node.list_channels()[0];
assert_eq!(
channel.channel_value_satoshis,
initial_channel_value_sat + funding_contribution.net_value().to_sat() as u64,
);
}
#[test]
fn test_fails_initiating_concurrent_splices() {
fails_initiating_concurrent_splices(true);
fails_initiating_concurrent_splices(false);
}
#[cfg(test)]
fn fails_initiating_concurrent_splices(reconnect: bool) {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let config = test_default_channel_config();
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 node_0_id = nodes[0].node.get_our_node_id();
let node_1_id = nodes[1].node.get_our_node_id();
send_payment(&nodes[0], &[&nodes[1]], 1_000);
provide_utxo_reserves(&nodes, 2, Amount::ONE_BTC);
let outputs = vec![TxOut {
value: Amount::from_sat(initial_channel_value_sat / 4),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_1_id).unwrap();
let funding_contribution =
funding_template.splice_out(outputs.clone(), feerate, FeeRate::MAX).unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_1_id, funding_contribution.clone(), None)
.unwrap();
assert_eq!(
nodes[0].node.splice_channel(&channel_id, &node_1_id),
Err(APIError::APIMisuseError {
err: format!(
"Channel {} cannot be spliced as one is waiting to be negotiated",
channel_id
),
}),
);
let new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
assert_eq!(
nodes[0].node.splice_channel(&channel_id, &node_1_id),
Err(APIError::APIMisuseError {
err: format!(
"Channel {} cannot be spliced as one is currently being negotiated",
channel_id
),
}),
);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution,
new_funding_script,
);
assert_eq!(
nodes[0].node.splice_channel(&channel_id, &node_1_id),
Err(APIError::APIMisuseError {
err: format!(
"Channel {} cannot be spliced as one is currently being negotiated",
channel_id
),
}),
);
let (splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]));
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_1_id);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.splice_channel(&channel_id, &node_1_id).is_ok());
if reconnect {
nodes[0].node.peer_disconnected(node_1_id);
nodes[1].node.peer_disconnected(node_0_id);
reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
}
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
let stfu = lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1).stfu;
assert!(stfu.is_none());
}
#[test]
fn test_initiating_splice_holds_stfu_with_pending_splice() {
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);
provide_utxo_reserves(&nodes, 2, Amount::ONE_BTC);
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 value_added = Amount::from_sat(10_000);
let funding_contribution_0 = initiate_splice_in(&nodes[0], &nodes[1], channel_id, value_added);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution_0);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
let stfu = lock_splice_after_blocks(&nodes[0], &nodes[1], 5).stfu;
assert!(stfu.is_none());
}
#[test]
fn test_splice_both_contribute_tiebreak() {
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
do_test_splice_tiebreak(feerate, feerate, Amount::from_sat(50_000), true);
}
#[test]
fn test_splice_tiebreak_higher_feerate() {
let feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
do_test_splice_tiebreak(
FeeRate::from_sat_per_kwu(feerate * 3),
FeeRate::from_sat_per_kwu(feerate),
Amount::from_sat(50_000),
true,
);
}
#[test]
fn test_splice_tiebreak_lower_feerate() {
let feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
do_test_splice_tiebreak(
FeeRate::from_sat_per_kwu(feerate),
FeeRate::from_sat_per_kwu(feerate * 3),
Amount::from_sat(50_000),
false,
);
}
#[test]
fn test_splice_tiebreak_feerate_too_high() {
let feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
do_test_splice_tiebreak(
FeeRate::from_sat_per_kwu(20_000),
FeeRate::from_sat_per_kwu(feerate),
Amount::from_sat(95_000),
false,
);
}
#[cfg(test)]
fn do_test_splice_tiebreak(
node_0_feerate: FeeRate, node_1_feerate: FeeRate, node_1_splice_value: Amount,
expect_acceptor_contributes: bool,
) {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution = funding_template_0
.splice_in_sync(added_value, node_0_feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution = funding_template_1
.splice_in_sync(node_1_splice_value, node_1_feerate, FeeRate::MAX, &wallet_1)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
assert!(stfu_0.initiator);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
assert!(stfu_1.initiator);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
let acceptor_contributes = splice_ack.funding_contribution_satoshis != 0;
assert_eq!(
acceptor_contributes, expect_acceptor_contributes,
"Expected acceptor contribution: {}, got: {}",
expect_acceptor_contributes, acceptor_contributes,
);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
if acceptor_contributes {
let node_0_change = node_0_funding_contribution
.change_output()
.expect("splice-in should have a change output")
.clone();
let node_1_change = node_1_funding_contribution
.change_output()
.expect("splice-in should have a change output")
.clone();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
let (tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
let initiator_change_in_tx = tx
.output
.iter()
.find(|o| o.script_pubkey == node_0_change.script_pubkey)
.expect("Initiator's change output should be in the splice transaction");
assert_eq!(
initiator_change_in_tx.value, node_0_change.value,
"Initiator's change output should remain unchanged",
);
let acceptor_change_in_tx = tx
.output
.iter()
.find(|o| o.script_pubkey == node_1_change.script_pubkey)
.expect("Acceptor's change output should be in the splice transaction");
if node_0_feerate <= node_1_feerate {
assert!(
acceptor_change_in_tx.value > node_1_change.value,
"Acceptor's change should increase when initiator feerate ({}) <= acceptor \
feerate ({}): adjusted {} vs original {}",
node_0_feerate.to_sat_per_kwu(),
node_1_feerate.to_sat_per_kwu(),
acceptor_change_in_tx.value,
node_1_change.value,
);
} else {
assert!(
acceptor_change_in_tx.value < node_1_change.value,
"Acceptor's change should decrease when initiator feerate ({}) > acceptor \
feerate ({}): adjusted {} vs original {}",
node_0_feerate.to_sat_per_kwu(),
node_1_feerate.to_sat_per_kwu(),
acceptor_change_in_tx.value,
node_1_change.value,
);
}
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
mine_transaction(&nodes[0], &tx);
mine_transaction(&nodes[1], &tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
} else {
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
None,
0,
new_funding_script,
);
let (tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]));
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
mine_transaction(&nodes[0], &tx);
mine_transaction(&nodes[1], &tx);
let node_1_stfu = lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1).stfu;
let stfu_1 = if let Some(MessageSendEvent::SendStfu { msg, .. }) = node_1_stfu {
assert!(msg.initiator);
msg
} else {
panic!("Expected SendStfu from node 1 after splice_locked");
};
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let splice_init = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceInit, node_id_0);
nodes[0].node.handle_splice_init(node_id_1, &splice_init);
let splice_ack = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceAck, node_id_1);
nodes[1].node.handle_splice_ack(node_id_0, &splice_ack);
let new_funding_script_2 = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation(
&nodes[1],
&nodes[0],
channel_id,
node_1_funding_contribution,
new_funding_script_2,
);
let (new_splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[1], &nodes[0]));
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[1], &node_id_0);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
mine_transaction(&nodes[1], &new_splice_tx);
mine_transaction(&nodes[0], &new_splice_tx);
lock_splice_after_blocks(&nodes[1], &nodes[0], ANTI_REORG_DELAY - 1);
}
}
#[test]
fn test_splice_tiebreak_feerate_too_high_rejected() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let high_feerate = FeeRate::from_sat_per_kwu(100_000);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let node_0_added_value = Amount::from_sat(50_000);
let node_1_added_value = Amount::from_sat(50_000);
let node_1_max_feerate = FeeRate::from_sat_per_kwu(3_000);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution = funding_template_0
.splice_in_sync(node_0_added_value, high_feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution = funding_template_1
.splice_in_sync(node_1_added_value, floor_feerate, node_1_max_feerate, &wallet_1)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2);
match &msg_events[0] {
MessageSendEvent::SendTxAbort { node_id, msg } => {
assert_eq!(*node_id, node_id_0);
assert_eq!(msg.channel_id, channel_id);
},
_ => panic!("Expected SendTxAbort, got {:?}", msg_events[0]),
};
match &msg_events[1] {
MessageSendEvent::SendStfu { node_id, .. } => {
assert_eq!(*node_id, node_id_0);
},
_ => panic!("Expected SendStfu, got {:?}", msg_events[1]),
};
}
#[cfg(test)]
#[derive(PartialEq)]
enum SpliceStatus {
Unconfirmed,
Confirmed,
Locked,
}
#[test]
fn test_splice_commitment_broadcast() {
do_test_splice_commitment_broadcast(SpliceStatus::Unconfirmed, false);
do_test_splice_commitment_broadcast(SpliceStatus::Unconfirmed, true);
do_test_splice_commitment_broadcast(SpliceStatus::Confirmed, false);
do_test_splice_commitment_broadcast(SpliceStatus::Confirmed, true);
do_test_splice_commitment_broadcast(SpliceStatus::Locked, false);
do_test_splice_commitment_broadcast(SpliceStatus::Locked, true);
}
#[cfg(test)]
fn do_test_splice_commitment_broadcast(splice_status: SpliceStatus, claim_htlcs: bool) {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, initial_funding_tx) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let coinbase_tx = provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let payment_amount = 1_000_000;
let (preimage1, payment_hash1, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount);
let splice_in_amount = initial_channel_capacity / 2;
let initiator_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, Amount::from_sat(splice_in_amount));
let (expected_discarded_inputs, expected_discarded_outputs) =
initiator_contribution.clone().into_contributed_inputs_and_outputs();
let (splice_tx, _) =
splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution.clone());
let (preimage2, payment_hash2, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount);
let htlc_expiry = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS;
if splice_status == SpliceStatus::Confirmed || splice_status == SpliceStatus::Locked {
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
}
if splice_status == SpliceStatus::Locked {
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
}
if claim_htlcs {
nodes[1].node.claim_funds(preimage1);
expect_payment_claimed!(&nodes[1], payment_hash1, payment_amount);
nodes[1].node.claim_funds(preimage2);
expect_payment_claimed!(&nodes[1], payment_hash2, payment_amount);
check_added_monitors(&nodes[1], 2);
let _ = get_htlc_update_msgs(&nodes[1], &node_id_0);
}
nodes[0]
.node
.force_close_broadcasting_latest_txn(&channel_id, &node_id_1, "test".to_owned())
.unwrap();
handle_bump_events(&nodes[0], true, 0);
let commitment_tx = {
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1);
let commitment_tx = txn.remove(0);
match splice_status {
SpliceStatus::Unconfirmed => check_spends!(&commitment_tx, &initial_funding_tx),
SpliceStatus::Confirmed | SpliceStatus::Locked => {
check_spends!(&commitment_tx, &splice_tx)
},
}
commitment_tx
};
mine_transaction(&nodes[0], &commitment_tx);
mine_transaction(&nodes[1], &commitment_tx);
let closure_reason = ClosureReason::HolderForceClosed {
broadcasted_latest_txn: Some(true),
message: "test".to_owned(),
};
let closed_channel_capacity = if splice_status == SpliceStatus::Locked {
initial_channel_capacity + initiator_contribution.net_value().to_sat() as u64
} else {
initial_channel_capacity
};
check_closed_event(&nodes[0], 1, closure_reason, &[node_id_1], closed_channel_capacity);
check_closed_broadcast(&nodes[0], 1, true);
check_added_monitors(&nodes[0], 1);
let closure_reason = ClosureReason::CommitmentTxConfirmed;
check_closed_event(&nodes[1], 1, closure_reason, &[node_id_0], closed_channel_capacity);
check_closed_broadcast(&nodes[1], 1, true);
check_added_monitors(&nodes[1], 1);
if !claim_htlcs {
connect_blocks(&nodes[0], htlc_expiry - nodes[0].best_block_info().1);
connect_blocks(&nodes[1], htlc_expiry - nodes[1].best_block_info().1);
expect_htlc_handling_failed_destinations!(
nodes[1].node.get_and_clear_pending_events(),
&[
HTLCHandlingFailureType::Receive { payment_hash: payment_hash1 },
HTLCHandlingFailureType::Receive { payment_hash: payment_hash2 }
]
);
}
let htlc_claim_tx = if claim_htlcs {
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1);
let htlc_success_tx = txn.remove(0);
assert_eq!(htlc_success_tx.input.len(), 2);
check_spends!(&htlc_success_tx, &commitment_tx);
htlc_success_tx
} else {
handle_bump_htlc_event(&nodes[0], 1);
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1);
let htlc_timeout_tx = txn.remove(0);
assert_eq!(htlc_timeout_tx.input.len(), 3);
let tx_with_fee_bump_utxo =
if splice_status == SpliceStatus::Unconfirmed { &coinbase_tx } else { &splice_tx };
check_spends!(&htlc_timeout_tx, &commitment_tx, tx_with_fee_bump_utxo);
htlc_timeout_tx
};
mine_transaction(&nodes[0], &htlc_claim_tx);
mine_transaction(&nodes[1], &htlc_claim_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let events = nodes[0].node.get_and_clear_pending_events();
if claim_htlcs {
assert_eq!(events.iter().filter(|e| matches!(e, Event::PaymentSent { .. })).count(), 2);
assert_eq!(
events.iter().filter(|e| matches!(e, Event::PaymentPathSuccessful { .. })).count(),
2
);
} else {
assert_eq!(events.iter().filter(|e| matches!(e, Event::PaymentFailed { .. })).count(), 2,);
assert_eq!(
events.iter().filter(|e| matches!(e, Event::PaymentPathFailed { .. })).count(),
2
);
}
check_added_monitors(&nodes[0], 2);
if splice_status == SpliceStatus::Unconfirmed {
connect_blocks(&nodes[0], BREAKDOWN_TIMEOUT as u32);
let (vout, txout) = splice_tx
.output
.iter()
.enumerate()
.find(|(_, output)| output.script_pubkey.is_p2wsh())
.unwrap();
let funding_outpoint = OutPoint { txid: splice_tx.compute_txid(), index: vout as u16 };
nodes[0]
.chain_source
.remove_watched_txn_and_outputs(funding_outpoint, txout.script_pubkey.clone());
nodes[1]
.chain_source
.remove_watched_txn_and_outputs(funding_outpoint, txout.script_pubkey.clone());
let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), if claim_htlcs { 2 } else { 4 }, "{events:?}");
if let Event::DiscardFunding { funding_info, .. } = &events[0] {
assert_eq!(
*funding_info,
FundingInfo::Contribution {
inputs: expected_discarded_inputs,
outputs: expected_discarded_outputs,
}
);
} else {
panic!();
}
assert!(matches!(&events[1], Event::SpendableOutputs { .. }));
if !claim_htlcs {
assert!(matches!(&events[2], Event::SpendableOutputs { .. }));
assert!(matches!(&events[3], Event::SpendableOutputs { .. }));
}
let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), if claim_htlcs { 2 } else { 1 }, "{events:?}");
if let Event::DiscardFunding { funding_info, .. } = &events[0] {
assert_eq!(*funding_info, FundingInfo::OutPoint { outpoint: funding_outpoint });
} else {
panic!();
}
if claim_htlcs {
assert!(matches!(&events[1], Event::SpendableOutputs { .. }));
}
}
}
#[test]
fn test_splice_reestablish() {
do_test_splice_reestablish(false, false);
do_test_splice_reestablish(false, true);
do_test_splice_reestablish(true, false);
do_test_splice_reestablish(true, true);
}
#[cfg(test)]
fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let (persister_0a, persister_0b, persister_1a, persister_1b);
let (chain_monitor_0a, chain_monitor_0b, chain_monitor_1a, chain_monitor_1b);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let (node_0a, node_0b, node_1a, node_1b);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_outpoint = get_monitor!(nodes[0], channel_id).get_funding_txo();
let prev_funding_script = get_monitor!(nodes[0], channel_id).get_funding_script();
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let 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(),
},
];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let _ = get_htlc_update_msgs(&nodes[1], &node_id_0);
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0a,
chain_monitor_0a,
node_0a
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1a,
chain_monitor_1a,
node_1a
);
let _ = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
let details = nodes[0]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 1);
assert!(matches!(
details.candidates[0].status,
SpliceCandidateStatus::AwaitingSignatures { .. }
));
assert!(details.candidates[0].contribution.is_some());
if async_monitor_update {
persister_0a.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
persister_1a.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
}
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
if async_monitor_update {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
}
}
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = signing_event {
let tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0].node.funding_transaction_signed(&channel_id, &node_id_1, tx).unwrap();
}
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_interactive_tx_commit_sig = (true, true);
reconnect_nodes(reconnect_args);
check_added_monitors(&nodes[0], 1);
check_added_monitors(&nodes[1], 1);
macro_rules! reconnect_nodes {
($f: expr) => {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
$f(&mut reconnect_args);
reconnect_nodes(reconnect_args);
};
}
if async_monitor_update {
reconnect_nodes!(|_| {});
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].chain_monitor.complete_sole_pending_chan_update(&channel_id);
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
}
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendTxSignatures, node_id_0);
reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
reconnect_args.send_interactive_tx_sigs = (true, false);
});
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
expect_splice_pending_event(&nodes[0], &node_id_1);
reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
reconnect_args.send_interactive_tx_sigs = (false, true);
});
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
if reload {
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0b,
chain_monitor_0b,
node_0b
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1b,
chain_monitor_1b,
node_1b
);
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
let splice_tx = {
let mut txn_0 = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn_0.len(), 1);
let txn_1 = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(txn_0, txn_1);
txn_0.remove(0)
};
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
confirm_transaction(&nodes[0], &splice_tx);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
mine_transaction(&nodes[1], &splice_tx);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
reconnect_nodes!(|_| {});
if async_monitor_update {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
}
connect_blocks(&nodes[1], 1);
check_added_monitors(&nodes[1], 1);
let mut msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::SendSpliceLocked { .. } = msg_events.remove(0) {
} else {
panic!()
}
if let MessageSendEvent::SendAnnouncementSignatures { .. } = msg_events.remove(0) {
} else {
panic!()
}
expect_channel_ready_event(&nodes[1], &node_id_0);
reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
reconnect_args.expect_renegotiated_funding_locked_monitor_update = (true, false);
reconnect_args.send_announcement_sigs = (true, true);
});
expect_channel_ready_event(&nodes[0], &node_id_1);
if async_monitor_update {
nodes[0].chain_monitor.complete_sole_pending_chan_update(&channel_id);
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
}
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
nodes[0]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
nodes[1]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
}
#[test]
fn test_reestablish_sends_tx_signatures_before_splice_locked() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_txid = get_monitor!(nodes[0], channel_id).get_funding_txo().txid;
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, funding_contribution);
let event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
let partially_signed_tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0]
.node
.funding_transaction_signed(&channel_id, &node_id_1, partially_signed_tx)
.unwrap();
} else {
panic!("Unexpected event {event:?}");
}
let commitment_update_0 = get_htlc_update_msgs(&nodes[0], &node_id_1);
nodes[1].node.handle_commitment_signed(node_id_0, &commitment_update_0.commitment_signed[0]);
check_added_monitors(&nodes[1], 1);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
assert!(updates.update_add_htlcs.is_empty());
assert_eq!(updates.commitment_signed.len(), 1);
nodes[0].node.handle_commitment_signed(node_id_1, &updates.commitment_signed[0]);
check_added_monitors(&nodes[0], 1);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[1] {
nodes[0].node.handle_tx_signatures(node_id_1, msg);
check_added_monitors(&nodes[0], 0);
expect_splice_pending_event(&nodes[0], &node_id_1);
} else {
panic!("Unexpected event {:?}", msg_events[1]);
}
let tx_signatures_0 = get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
let splice_txid = tx_signatures_0.tx_hash;
let mut broadcast_transactions = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(broadcast_transactions.len(), 1, "{broadcast_transactions:?}");
let splice_tx = broadcast_transactions.remove(0);
assert_eq!(splice_tx.compute_txid(), splice_txid);
assert!(nodes[1].tx_broadcaster.txn_broadcast().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
connect_nodes(&nodes[0], &nodes[1]);
let reestablish_0 =
get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, node_id_1);
let reestablish_1 =
get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, node_id_0);
assert!(reestablish_0.next_funding.is_none());
assert_ne!(
reestablish_0.my_current_funding_locked.as_ref().map(|funding| funding.txid),
Some(splice_txid),
);
assert_eq!(reestablish_1.next_funding.as_ref().map(|funding| funding.txid), Some(splice_txid));
confirm_transaction(&nodes[0], &splice_tx);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
nodes[1].node.handle_channel_reestablish(node_id_0, &reestablish_0);
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, node_id_0);
nodes[0].node.handle_channel_reestablish(node_id_1, &reestablish_1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 3, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[0] {
nodes[1].node.handle_tx_signatures(node_id_0, &msg);
check_added_monitors(&nodes[1], 0);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
let splice_locked_0 = if let MessageSendEvent::SendSpliceLocked { msg, .. } = &msg_events[1] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
assert!(matches!(msg_events[2], MessageSendEvent::SendChannelUpdate { .. }));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let broadcast_transactions = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(broadcast_transactions.len(), 1, "{broadcast_transactions:?}");
assert_eq!(broadcast_transactions[0], splice_tx);
confirm_transaction(&nodes[1], &splice_tx);
complete_splice_locked_exchange(
&nodes[0],
&nodes[1],
&splice_locked_0,
false,
&[],
prev_funding_txid,
);
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn test_promoted_splice_locked_sent_after_channel_reestablish() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_txo = get_monitor!(nodes[0], channel_id).get_funding_txo();
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let payment_amount = 1_000_000;
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
let onion = RecipientOnionFields::secret_only(payment_secret, payment_amount);
let payment_id = PaymentId(payment_hash.0);
nodes[0].node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let update = get_htlc_update_msgs(&nodes[0], &node_id_1);
check_added_monitors(&nodes[0], 1);
nodes[1].node.handle_update_add_htlc(node_id_0, &update.update_add_htlcs[0]);
nodes[1].node.handle_commitment_signed_batch_test(node_id_0, &update.commitment_signed);
check_added_monitors(&nodes[1], 1);
let (_dropped_raa, dropped_commitment_signed) = get_revoke_commit_msgs(&nodes[1], &node_id_0);
assert!(dropped_commitment_signed.len() > 1, "{dropped_commitment_signed:?}");
confirm_transaction(&nodes[0], &splice_tx);
let splice_locked_0 = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
connect_nodes(&nodes[0], &nodes[1]);
let reestablish_0 =
get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, node_id_1);
let reestablish_1 =
get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, node_id_0);
confirm_transaction(&nodes[1], &splice_tx);
check_added_monitors(&nodes[1], 1);
let new_funding_txo =
get_monitor!(nodes[1], channel_id).get_funding_txo().into_bitcoin_outpoint();
let channel_ready_1 = get_event!(&nodes[1], Event::ChannelReady);
assert!(matches!(
channel_ready_1, Event::ChannelReady { funding_txo, .. }
if funding_txo == Some(new_funding_txo)
));
nodes[1].node.handle_channel_reestablish(node_id_0, &reestablish_0);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 5, "{msg_events:?}");
assert!(matches!(&msg_events[0], MessageSendEvent::SendAnnouncementSignatures { .. }));
let splice_locked_1 = if let MessageSendEvent::SendSpliceLocked { msg, .. } = &msg_events[1] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
let revoke_and_ack = if let MessageSendEvent::SendRevokeAndACK { msg, .. } = &msg_events[2] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[2]);
};
let commit_sig = if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[3] {
assert_eq!(updates.commitment_signed.len(), 1);
updates.commitment_signed.first().unwrap()
} else {
panic!("Unexpected event {:?}", msg_events[3]);
};
assert!(matches!(&msg_events[4], MessageSendEvent::SendChannelUpdate { .. }));
nodes[0].node.handle_channel_reestablish(node_id_1, &reestablish_1);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, node_id_1);
nodes[0].node.handle_splice_locked(node_id_1, splice_locked_1);
check_added_monitors(&nodes[0], 1);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendAnnouncementSignatures, node_id_1);
let channel_ready_0 = get_event!(&nodes[0], Event::ChannelReady);
assert!(matches!(
channel_ready_0, Event::ChannelReady { funding_txo, .. }
if funding_txo == Some(new_funding_txo)
));
nodes[0].node.handle_revoke_and_ack(node_id_1, revoke_and_ack);
check_added_monitors(&nodes[0], 1);
nodes[0].node.handle_commitment_signed(node_id_1, commit_sig);
check_added_monitors(&nodes[0], 1);
let revoke_and_ack = get_event_msg!(&nodes[0], MessageSendEvent::SendRevokeAndACK, node_id_1);
nodes[1].node.handle_revoke_and_ack(node_id_0, &revoke_and_ack);
check_added_monitors(&nodes[1], 1);
nodes[1].node.process_pending_htlc_forwards();
expect_payment_claimable!(&nodes[1], payment_hash, payment_secret, payment_amount);
send_payment(&nodes[0], &[&nodes[1]], payment_amount);
for node in [&nodes[0], &nodes[1]] {
node.chain_source.remove_watched_by_txid(prev_funding_txo.txid);
}
}
#[test]
fn test_splice_reestablish_waits_for_holder_tx_signatures_before_commitment_signed() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 outputs = vec![TxOut {
value: Amount::from_sat(initial_channel_value_sat / 4),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let _ = get_htlc_update_msgs(&nodes[1], &node_id_0);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_args.send_interactive_tx_commit_sig = (true, false);
reconnect_nodes(reconnect_args);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let unsigned_transaction = if let Event::FundingTransactionReadyForSigning {
unsigned_transaction,
..
} = signing_event
{
unsigned_transaction
} else {
panic!("Expected FundingTransactionReadyForSigning event");
};
let tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0].node.funding_transaction_signed(&channel_id, &node_id_1, tx).unwrap();
check_added_monitors(&nodes[0], 1);
let initiator_commit_sig = get_htlc_update_msgs(&nodes[0], &node_id_1);
nodes[1]
.node
.handle_commitment_signed_batch_test(node_id_0, &initiator_commit_sig.commitment_signed);
check_added_monitors(&nodes[1], 1);
let acceptor_tx_signatures =
get_event_msg!(nodes[1], MessageSendEvent::SendTxSignatures, node_id_0);
nodes[0].node.handle_tx_signatures(node_id_1, &acceptor_tx_signatures);
let initiator_tx_signatures =
get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
nodes[1].node.handle_tx_signatures(node_id_0, &initiator_tx_signatures);
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
#[test]
fn test_splice_reestablish_sends_commitment_signed_before_tx_signatures() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 outputs = vec![TxOut {
value: Amount::from_sat(initial_channel_value_sat / 4),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let acceptor_commit_sig = get_htlc_update_msgs(&nodes[1], &node_id_0);
assert_eq!(acceptor_commit_sig.commitment_signed.len(), 1);
let unsigned_transaction = if let Event::FundingTransactionReadyForSigning {
unsigned_transaction,
..
} = signing_event
{
unsigned_transaction
} else {
panic!("Expected FundingTransactionReadyForSigning event");
};
let tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0].node.funding_transaction_signed(&channel_id, &node_id_1, tx).unwrap();
check_added_monitors(&nodes[0], 0);
let initiator_commit_sig = get_htlc_update_msgs(&nodes[0], &node_id_1);
nodes[1]
.node
.handle_commitment_signed_batch_test(node_id_0, &initiator_commit_sig.commitment_signed);
check_added_monitors(&nodes[1], 1);
let _ = get_event_msg!(&nodes[1], MessageSendEvent::SendTxSignatures, node_id_0);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
connect_nodes(&nodes[0], &nodes[1]);
let reestablish_0 =
get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, node_id_1);
let _reestablish_1 =
get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, node_id_0);
let next_funding = reestablish_0.next_funding.as_ref().expect("next_funding should be set");
assert!(next_funding.should_retransmit(msgs::NextFundingFlag::CommitmentSigned));
nodes[1].node.handle_channel_reestablish(node_id_0, &reestablish_0);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
let commitment_update_idx = msg_events
.iter()
.position(|event| {
matches!(event, MessageSendEvent::UpdateHTLCs { updates, .. }
if updates.commitment_signed.len() == 1)
})
.expect("commitment_signed should be retransmitted");
let tx_signatures_idx = msg_events
.iter()
.position(|event| matches!(event, MessageSendEvent::SendTxSignatures { .. }))
.expect("tx_signatures should be retransmitted");
assert!(
commitment_update_idx < tx_signatures_idx,
"commitment_signed should be retransmitted before tx_signatures: {msg_events:?}"
);
let commitment_signed =
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[commitment_update_idx] {
updates.commitment_signed.clone()
} else {
panic!("Expected UpdateHTLCs");
};
let tx_signatures =
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[tx_signatures_idx] {
msg.clone()
} else {
panic!("Expected SendTxSignatures");
};
nodes[0].node.handle_commitment_signed_batch_test(node_id_1, &commitment_signed);
check_added_monitors(&nodes[0], 1);
nodes[0].node.handle_tx_signatures(node_id_1, &tx_signatures);
let initiator_tx_signatures =
get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
nodes[1].node.handle_tx_signatures(node_id_0, &initiator_tx_signatures);
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
#[test]
fn test_splice_confirms_on_both_sides_while_disconnected() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_outpoint = get_monitor!(nodes[0], channel_id).get_funding_txo();
let prev_funding_script = get_monitor!(nodes[0], channel_id).get_funding_script();
let pre_splice_scid = nodes[0].node.list_channels()[0].short_channel_id.unwrap();
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
confirm_transaction(&nodes[0], &splice_tx);
confirm_transaction(&nodes[1], &splice_tx);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
connect_nodes(&nodes[0], &nodes[1]);
let reestablish_0 = get_chan_reestablish_msgs!(nodes[0], nodes[1]);
let reestablish_1 = get_chan_reestablish_msgs!(nodes[1], nodes[0]);
for msg in &reestablish_0 {
nodes[1].node.handle_channel_reestablish(node_id_0, msg);
}
for msg in &reestablish_1 {
nodes[0].node.handle_channel_reestablish(node_id_1, msg);
}
check_added_monitors(&nodes[0], 1);
check_added_monitors(&nodes[1], 1);
expect_channel_ready_event(&nodes[0], &node_id_1);
expect_channel_ready_event(&nodes[1], &node_id_0);
let take_announcement_sigs = |events: Vec<MessageSendEvent>| -> msgs::AnnouncementSignatures {
let mut sigs = events.into_iter().filter_map(|e| match e {
MessageSendEvent::SendAnnouncementSignatures { msg, .. } => Some(msg),
_ => None,
});
let only = sigs.next().expect("expected one SendAnnouncementSignatures");
assert!(sigs.next().is_none(), "expected only one SendAnnouncementSignatures");
only
};
let node_0_events = nodes[0].node.get_and_clear_pending_msg_events();
let node_1_events = nodes[1].node.get_and_clear_pending_msg_events();
let node_0_sigs = take_announcement_sigs(node_0_events);
let node_1_sigs = take_announcement_sigs(node_1_events);
assert_ne!(node_0_sigs.short_channel_id, pre_splice_scid);
assert_ne!(node_1_sigs.short_channel_id, pre_splice_scid);
nodes[1].node.handle_announcement_signatures(node_id_0, &node_0_sigs);
nodes[0].node.handle_announcement_signatures(node_id_1, &node_1_sigs);
let _ = nodes[0].node.get_and_clear_pending_msg_events();
let _ = nodes[1].node.get_and_clear_pending_msg_events();
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
nodes[1]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
}
#[test]
fn test_holding_cell_claim_freed_after_inferred_splice_locked() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_outpoint = get_monitor!(nodes[0], channel_id).get_funding_txo();
let prev_funding_script = get_monitor!(nodes[0], channel_id).get_funding_script();
let prev_scid = nodes[0].node.list_channels()[0].short_channel_id;
let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
nodes[1].node.claim_funds(payment_preimage);
check_added_monitors(&nodes[1], 1);
expect_payment_claimed!(nodes[1], payment_hash, 1_000_000);
confirm_transaction(&nodes[0], &splice_tx);
confirm_transaction(&nodes[1], &splice_tx);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.expect_renegotiated_funding_locked_monitor_update = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
expect_channel_ready_event(&nodes[0], &node_id_1);
expect_channel_ready_event(&nodes[1], &node_id_0);
assert_ne!(prev_scid, nodes[0].node.list_channels()[0].short_channel_id);
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
let mut commitment_update = get_htlc_update_msgs(&nodes[1], &node_id_0);
check_added_monitors(&nodes[1], 1);
nodes[0]
.node
.handle_update_fulfill_htlc(node_id_1, commitment_update.update_fulfill_htlcs.remove(0));
do_commitment_signed_dance(
&nodes[0],
&nodes[1],
&commitment_update.commitment_signed,
false,
false,
);
expect_payment_sent!(nodes[0], payment_preimage);
nodes[0]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
nodes[1]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
}
#[test]
fn test_stale_monitor_pending_resends_cleared_by_reestablish() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let persister;
let chain_monitor;
let node_1_reload;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 prev_funding_outpoint = get_monitor!(nodes[1], channel_id).get_funding_txo();
let prev_funding_script = get_monitor!(nodes[1], channel_id).get_funding_script();
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
confirm_transaction(&nodes[0], &splice_tx);
let splice_locked_0 = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let payment_amount = 1_000_000;
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
let onion = RecipientOnionFields::secret_only(payment_secret, payment_amount);
let payment_id = PaymentId(payment_hash.0);
nodes[0].node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let htlc_update = get_htlc_update_msgs(&nodes[0], &node_id_1);
check_added_monitors(&nodes[0], 1);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[1].node.handle_update_add_htlc(node_id_0, &htlc_update.update_add_htlcs[0]);
nodes[1].node.handle_commitment_signed_batch_test(node_id_0, &htlc_update.commitment_signed);
check_added_monitors(&nodes[1], 1);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let stale_manager_1 = nodes[1].node.encode();
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
let (raa, commitment_signed) = get_revoke_commit_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_revoke_and_ack(node_id_1, &raa);
check_added_monitors(&nodes[0], 1);
nodes[0].node.handle_commitment_signed_batch_test(node_id_1, &commitment_signed);
check_added_monitors(&nodes[0], 1);
let _dropped_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, node_id_1);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let latest_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&stale_manager_1,
&[&latest_monitor_1],
persister,
chain_monitor,
node_1_reload
);
mine_transaction_without_consistency_checks(&nodes[1], &splice_tx);
connect_blocks(&nodes[1], 5);
persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
connect_nodes(&nodes[0], &nodes[1]);
check_added_monitors(&nodes[1], 1);
let reestablish_0 = get_chan_reestablish_msgs!(nodes[0], nodes[1]);
let reestablish_1 = get_chan_reestablish_msgs!(nodes[1], nodes[0]);
nodes[1].node.handle_channel_reestablish(node_id_0, reestablish_0.first().unwrap());
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert!(
msg_events.iter().all(|event| !matches!(
event,
MessageSendEvent::SendRevokeAndACK { .. } | MessageSendEvent::UpdateHTLCs { .. }
)),
"stale monitor-pending resend leaked during reestablish: {msg_events:?}"
);
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert!(
msg_events.iter().all(|event| !matches!(
event,
MessageSendEvent::SendRevokeAndACK { .. } | MessageSendEvent::UpdateHTLCs { .. }
)),
"stale monitor-pending resend leaked after reestablish: {msg_events:?}"
);
expect_channel_ready_event(&nodes[1], &node_id_0);
nodes[0].node.handle_channel_reestablish(node_id_1, reestablish_1.first().unwrap());
check_added_monitors(&nodes[0], 1);
expect_channel_ready_event(&nodes[0], &node_id_1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 3, "{msg_events:?}");
if let MessageSendEvent::SendRevokeAndACK { msg, .. } = &msg_events[0] {
nodes[1].node.handle_revoke_and_ack(node_id_0, msg);
check_added_monitors(&nodes[1], 1);
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[1].node.process_pending_htlc_forwards();
expect_payment_claimable!(&nodes[1], payment_hash, payment_secret, payment_amount);
} else {
panic!("Unexpected event {:?}", &msg_events[0]);
}
send_payment(&nodes[0], &[&nodes[1]], payment_amount);
for node in &[&nodes[0], &nodes[1]] {
node.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
}
}
#[test]
fn test_stale_announcement_signatures_ignored_after_splice_lock() {
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let chan_announcement =
create_chan_between_nodes_with_value(&nodes[0], &nodes[1], initial_channel_value_sat, 0);
let channel_id = chan_announcement.3;
update_nodes_with_chan_announce(
&nodes,
0,
1,
&chan_announcement.0,
&chan_announcement.1,
&chan_announcement.2,
);
let node_1_is_node_one = node_id_1.serialize() < node_id_0.serialize();
let (stale_node_sig, stale_bitcoin_sig) = if node_1_is_node_one {
(chan_announcement.0.node_signature_1, chan_announcement.0.bitcoin_signature_1)
} else {
(chan_announcement.0.node_signature_2, chan_announcement.0.bitcoin_signature_2)
};
let pre_splice_scid = nodes[0].node.list_channels()[0].short_channel_id.unwrap();
let 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(),
},
];
let funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
let post_splice_scid = nodes[0].node.list_channels()[0].short_channel_id.unwrap();
assert_ne!(pre_splice_scid, post_splice_scid);
let stale_sigs = msgs::AnnouncementSignatures {
channel_id,
short_channel_id: pre_splice_scid,
node_signature: stale_node_sig,
bitcoin_signature: stale_bitcoin_sig,
};
nodes[0].node.handle_announcement_signatures(node_id_1, &stale_sigs);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert_eq!(nodes[0].node.list_channels().len(), 1);
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn test_propose_splice_while_disconnected() {
do_test_propose_splice_while_disconnected(false);
do_test_propose_splice_while_disconnected(true);
}
#[cfg(test)]
fn do_test_propose_splice_while_disconnected(use_0conf: bool) {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut config = test_default_channel_config();
if use_0conf {
config.channel_handshake_limits.trust_own_funding_0conf = true;
}
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_value_sat = 1_000_000;
let push_msat = initial_channel_value_sat / 2 * 1000;
let channel_id = if use_0conf {
let (funding_tx, channel_id) = open_zero_conf_channel_with_value(
&nodes[0],
&nodes[1],
None,
initial_channel_value_sat,
push_msat,
);
mine_transaction(&nodes[0], &funding_tx);
mine_transaction(&nodes[1], &funding_tx);
channel_id
} else {
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
0,
1,
initial_channel_value_sat,
push_msat,
);
channel_id
};
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let splice_out_sat = initial_channel_value_sat / 4;
let node_0_outputs = vec![TxOut {
value: Amount::from_sat(splice_out_sat),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let node_0_funding_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, node_0_outputs).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let node_1_outputs = vec![TxOut {
value: Amount::from_sat(splice_out_sat),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
let node_1_funding_contribution =
initiate_splice_out(&nodes[1], &nodes[0], channel_id, node_1_outputs).unwrap();
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
if !use_0conf {
reconnect_args.send_announcement_sigs = (true, true);
}
reconnect_args.send_stfu = (true, true);
reconnect_nodes(reconnect_args);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let (prev_funding_outpoint, prev_funding_script) = nodes[0]
.chain_monitor
.chain_monitor
.get_monitor(channel_id)
.map(|monitor| (monitor.get_funding_txo(), monitor.get_funding_script()))
.unwrap();
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
let mut args =
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution();
if use_0conf {
args = args.zero_conf();
}
let (splice_tx, splice_locked) = sign_interactive_funding_tx(args);
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
let splice_locked = if use_0conf {
let (splice_locked, for_node_id) = splice_locked.unwrap();
assert_eq!(for_node_id, node_id_1);
splice_locked
} else {
assert!(splice_locked.is_none());
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1)
};
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if use_0conf { 1 } else { 2 }, "{msg_events:?}");
if let MessageSendEvent::SendSpliceLocked { ref msg, .. } = &msg_events[0] {
nodes[0].node.handle_splice_locked(node_id_1, msg);
if use_0conf {
let txn_0 = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn_0.len(), 1);
assert_eq!(&txn_0[0], &splice_tx);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
}
} else {
panic!("Unexpected event {:?}", &msg_events[0]);
}
if !use_0conf {
if let MessageSendEvent::SendAnnouncementSignatures { ref msg, .. } = &msg_events[1] {
nodes[0].node.handle_announcement_signatures(node_id_1, msg);
} else {
panic!("Unexpected event {:?}", &msg_events[1]);
}
}
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if use_0conf { 0 } else { 2 }, "{msg_events:?}");
if !use_0conf {
if let MessageSendEvent::SendAnnouncementSignatures { ref msg, .. } = &msg_events[0] {
nodes[1].node.handle_announcement_signatures(node_id_0, msg);
} else {
panic!("Unexpected event {:?}", &msg_events[1]);
}
assert!(matches!(&msg_events[1], MessageSendEvent::BroadcastChannelAnnouncement { .. }));
}
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if use_0conf { 0 } else { 1 }, "{msg_events:?}");
if !use_0conf {
assert!(matches!(&msg_events[0], MessageSendEvent::BroadcastChannelAnnouncement { .. }));
}
expect_channel_ready_event(&nodes[0], &node_id_1);
check_added_monitors(&nodes[0], 1);
expect_channel_ready_event(&nodes[1], &node_id_0);
check_added_monitors(&nodes[1], 1);
nodes[0]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
nodes[1]
.chain_source
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn disconnect_on_unexpected_interactive_tx_message() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let splice_in_amount = initial_channel_capacity / 2;
let contribution =
initiate_splice_in(initiator, acceptor, channel_id, Amount::from_sat(splice_in_amount));
negotiate_splice_tx(initiator, acceptor, channel_id, contribution);
let _ = get_event!(initiator, Event::FundingTransactionReadyForSigning);
let _ = get_htlc_update_msgs(acceptor, &node_id_initiator);
let tx_complete = msgs::TxComplete { channel_id };
initiator.node.handle_tx_complete(node_id_acceptor, &tx_complete);
let _warning = get_warning_msg(initiator, &node_id_acceptor);
}
#[test]
fn fail_splice_on_interactive_tx_error() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let splice_in_amount = initial_channel_capacity / 2;
let funding_contribution =
initiate_splice_in(initiator, acceptor, channel_id, Amount::from_sat(splice_in_amount));
let _ = complete_splice_handshake(initiator, acceptor);
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(initiator, acceptor, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
initiator.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let tx_add_input =
get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
acceptor.node.handle_tx_add_input(node_id_initiator, &tx_add_input);
let _tx_complete =
get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
initiator.node.handle_tx_add_input(node_id_acceptor, &tx_add_input);
expect_splice_failed_events(
initiator,
&channel_id,
funding_contribution,
NegotiationFailureReason::NegotiationError {
msg: "Abort: Parity for `serial_id` was incorrect".to_string(),
},
);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
let tx_abort = if let MessageSendEvent::SendTxAbort { msg, .. } = &msg_events[0] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[0]);
};
let update = if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[1] {
updates
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
check_added_monitors(initiator, 1);
acceptor.node.handle_tx_abort(node_id_initiator, tx_abort);
let tx_abort = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
acceptor.node.handle_update_add_htlc(node_id_initiator, &update.update_add_htlcs[0]);
do_commitment_signed_dance(acceptor, initiator, &update.commitment_signed, false, false);
}
#[test]
fn fail_splice_on_tx_abort() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let splice_in_amount = initial_channel_capacity / 2;
let funding_contribution =
initiate_splice_in(initiator, acceptor, channel_id, Amount::from_sat(splice_in_amount));
let _ = complete_splice_handshake(initiator, acceptor);
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(initiator, acceptor, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
initiator.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let tx_add_input =
get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
acceptor.node.handle_tx_add_input(node_id_initiator, &tx_add_input);
let _tx_complete =
get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
let tx_abort = msgs::TxAbort { channel_id, data: Vec::new() };
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
expect_splice_failed_events(
initiator,
&channel_id,
funding_contribution,
NegotiationFailureReason::CounterpartyAborted { msg: UntrustedString(String::new()) },
);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
check_added_monitors(initiator, 1);
if let MessageSendEvent::SendTxAbort { msg, .. } = &msg_events[0] {
acceptor.node.handle_tx_abort(node_id_initiator, msg);
let _ = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
};
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[1] {
acceptor.node.handle_update_add_htlc(node_id_initiator, &updates.update_add_htlcs[0]);
do_commitment_signed_dance(acceptor, initiator, &updates.commitment_signed, false, false);
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
}
#[test]
fn acceptor_with_local_contribution_can_cancel_funding_contributed_before_funding_transaction_signed(
) {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
provide_utxo_reserves(&nodes, 2, Amount::ONE_BTC);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let acceptor_contribution = initiate_splice_in(
acceptor,
initiator,
channel_id,
Amount::from_sat(initial_channel_capacity / 2),
);
let stfu_initiator = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
let stfu_acceptor = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
acceptor.node.handle_stfu(node_id_initiator, &stfu_initiator);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
initiator.node.handle_stfu(node_id_acceptor, &stfu_acceptor);
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);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
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();
complete_interactive_funding_negotiation_for_both(
initiator,
acceptor,
channel_id,
initiator_contribution.clone(),
Some(acceptor_contribution.clone()),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
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();
} else {
unreachable!();
}
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
let initial_commit_sig = if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
updates.commitment_signed[0].clone()
} else {
panic!("Unexpected event {:?}", msg_events[0]);
};
acceptor.node.handle_commitment_signed(node_id_initiator, &initial_commit_sig);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
let _signing_event = get_event!(acceptor, Event::FundingTransactionReadyForSigning);
acceptor.node.cancel_funding_contributed(&channel_id, &node_id_initiator).unwrap();
let events = acceptor.node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
assert!(matches!(events[0], Event::DiscardFunding { .. }));
assert!(matches!(events[1], Event::SpliceNegotiationFailed { .. }));
let tx_abort = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
let reason = NegotiationFailureReason::CounterpartyAborted {
msg: UntrustedString("Manually aborted funding negotiation".into()),
};
expect_splice_failed_events(initiator, &channel_id, initiator_contribution, reason);
let tx_abort = get_event_msg!(initiator, MessageSendEvent::SendTxAbort, node_id_acceptor);
acceptor.node.handle_tx_abort(node_id_initiator, &tx_abort);
}
#[test]
fn acceptor_can_cancel_queued_funding_contributed_during_counterparty_splice() {
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);
let acceptor = &nodes[0];
let initiator = &nodes[1];
let node_id_acceptor = acceptor.node.get_our_node_id();
let node_id_initiator = initiator.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let initiator_contribution =
do_initiate_splice_in(initiator, acceptor, channel_id, added_value);
let stfu_initiator = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
acceptor.node.handle_stfu(node_id_initiator, &stfu_initiator);
let stfu_acceptor = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
initiator.node.handle_stfu(node_id_acceptor, &stfu_acceptor);
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);
assert_eq!(splice_ack.funding_contribution_satoshis, 0);
let funding_template = acceptor.node.splice_channel(&channel_id, &node_id_initiator).unwrap();
let feerate = funding_template.min_rbf_feerate().unwrap();
let wallet = WalletSync::new(Arc::clone(&acceptor.wallet_source), acceptor.logger);
let queued_contribution = funding_template
.splice_in_sync(Amount::from_sat(25_000), feerate, FeeRate::MAX, &wallet)
.unwrap();
acceptor
.node
.funding_contributed(&channel_id, &node_id_initiator, queued_contribution.clone(), None)
.unwrap();
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
let details = acceptor
.node
.list_channels()
.into_iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.unwrap();
assert_eq!(details.candidates.len(), 2);
assert!(matches!(
details.candidates[0].status,
SpliceCandidateStatus::ConstructingTransaction { is_initiator: false, .. }
));
assert_eq!(details.candidates[0].contribution, None);
assert_eq!(details.candidates[1].status, SpliceCandidateStatus::WaitingOnQuiescence);
assert_eq!(details.candidates[1].contribution, Some(queued_contribution.clone()));
acceptor.node.cancel_funding_contributed(&channel_id, &node_id_initiator).unwrap();
let reason = NegotiationFailureReason::LocallyCanceled;
expect_splice_failed_events(acceptor, &channel_id, queued_contribution, reason);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
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();
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
initiator_contribution,
new_funding_script,
);
let (splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(initiator, acceptor));
assert!(splice_locked.is_none());
expect_splice_pending_event(initiator, &node_id_acceptor);
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
mine_transaction(initiator, &splice_tx);
mine_transaction(acceptor, &splice_tx);
assert!(lock_splice_after_blocks(initiator, acceptor, ANTI_REORG_DELAY - 1).stfu.is_none());
}
#[test]
fn cancel_funding_contributed_before_funding_transaction_signed() {
do_cancel_funding_contributed_before_funding_transaction_signed(0); do_cancel_funding_contributed_before_funding_transaction_signed(1); do_cancel_funding_contributed_before_funding_transaction_signed(2); do_cancel_funding_contributed_before_funding_transaction_signed(3); }
#[cfg(test)]
fn do_cancel_funding_contributed_before_funding_transaction_signed(state: u8) {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
match state {
0 => {
},
1 => {
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);
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
assert!(matches!(msg_events[0], MessageSendEvent::SendSpliceAck { .. }));
},
2 => {
let _new_funding_script = complete_splice_handshake(initiator, acceptor);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
assert!(matches!(msg_events[0], MessageSendEvent::SendTxAddInput { .. }));
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
},
3 => {
let new_funding_script = complete_splice_handshake(initiator, acceptor);
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
funding_contribution.clone(),
new_funding_script,
);
let _signing_event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
let acceptor_commit_sig = get_htlc_update_msgs(acceptor, &node_id_initiator);
initiator.node.handle_commitment_signed(
node_id_acceptor,
&acceptor_commit_sig.commitment_signed[0],
);
check_added_monitors(initiator, 0);
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
},
_ => panic!("unexpected state {state}"),
}
assert!(initiator.node.get_and_clear_pending_events().is_empty());
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
if state != 0 {
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(initiator, acceptor, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
initiator.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
}
initiator.node.cancel_funding_contributed(&channel_id, &node_id_acceptor).unwrap();
let reason = NegotiationFailureReason::LocallyCanceled;
expect_splice_failed_events(initiator, &channel_id, funding_contribution, reason);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if state == 0 {
if let MessageSendEvent::SendStfu { .. } = &msg_events[0] {
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
if let MessageSendEvent::HandleError { action, .. } = &msg_events[1] {
assert!(matches!(action, msgs::ErrorAction::DisconnectPeerWithWarning { .. }));
} else {
panic!("Unexpected event {:?}", msg_events[1]);
}
return;
}
let tx_abort = if let MessageSendEvent::SendTxAbort { msg, .. } = &msg_events[0] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[0]);
};
let update = if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[1] {
updates
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
check_added_monitors(initiator, 1);
acceptor.node.handle_tx_abort(node_id_initiator, tx_abort);
let tx_abort = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
acceptor.node.handle_update_add_htlc(node_id_initiator, &update.update_add_htlcs[0]);
do_commitment_signed_dance(acceptor, initiator, &update.commitment_signed, false, false);
}
#[test]
fn cancel_funding_contributed_then_inflight_commitment_signed_does_not_close_channel() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let new_funding_script = complete_splice_handshake(initiator, acceptor);
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
funding_contribution.clone(),
new_funding_script,
);
let _ = get_event!(initiator, Event::FundingTransactionReadyForSigning);
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
let acceptor_commit_sig = get_htlc_update_msgs(acceptor, &node_id_initiator);
assert_eq!(acceptor_commit_sig.commitment_signed.len(), 1);
initiator.node.cancel_funding_contributed(&channel_id, &node_id_acceptor).unwrap();
let reason = NegotiationFailureReason::LocallyCanceled;
expect_splice_failed_events(initiator, &channel_id, funding_contribution, reason);
let tx_abort = get_event_msg!(initiator, MessageSendEvent::SendTxAbort, node_id_acceptor);
assert_eq!(tx_abort.channel_id, channel_id);
initiator
.node
.handle_commitment_signed(node_id_acceptor, &acceptor_commit_sig.commitment_signed[0]);
assert!(initiator.node.get_and_clear_pending_events().is_empty());
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
}
#[test]
fn cannot_cancel_funding_contributed_after_funding_transaction_signed() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let new_funding_script = complete_splice_handshake(initiator, acceptor);
complete_interactive_funding_negotiation(
initiator,
acceptor,
channel_id,
funding_contribution,
new_funding_script,
);
assert!(acceptor.node.get_and_clear_pending_events().is_empty());
let _acceptor_commit_sig = get_htlc_update_msgs(acceptor, &node_id_initiator);
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
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();
} else {
unreachable!();
}
let res = initiator.node.cancel_funding_contributed(&channel_id, &node_id_acceptor);
match res {
Err(APIError::APIMisuseError { err }) => assert!(err.contains("already signed")),
_ => panic!("Unexpected result {res:?}"),
}
assert!(initiator.node.get_and_clear_pending_events().is_empty());
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert!(
msg_events.iter().all(|event| !matches!(event, MessageSendEvent::SendTxAbort { .. })),
"{msg_events:?}"
);
}
#[test]
fn fail_splice_on_tx_complete_error() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let config = test_default_channel_config();
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let initiator = &nodes[1];
let acceptor = &nodes[0];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: acceptor.wallet_source.get_change_script().unwrap(),
}];
let funding_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let _ = complete_splice_handshake(initiator, acceptor);
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(initiator, acceptor, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
acceptor.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let tx_add_input =
get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
acceptor.node.handle_tx_add_input(node_id_initiator, &tx_add_input);
let tx_complete = get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
initiator.node.handle_tx_complete(node_id_acceptor, &tx_complete);
let mut tx_add_output =
get_event_msg!(initiator, MessageSendEvent::SendTxAddOutput, node_id_acceptor);
if tx_add_output.script.is_p2wsh() {
tx_add_output.sats *= 2;
}
acceptor.node.handle_tx_add_output(node_id_initiator, &tx_add_output);
let tx_complete = get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
initiator.node.handle_tx_complete(node_id_acceptor, &tx_complete);
let mut tx_add_output =
get_event_msg!(initiator, MessageSendEvent::SendTxAddOutput, node_id_acceptor);
if tx_add_output.script.is_p2wsh() {
tx_add_output.sats *= 2;
}
acceptor.node.handle_tx_add_output(node_id_initiator, &tx_add_output);
let tx_complete = get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
initiator.node.handle_tx_complete(node_id_acceptor, &tx_complete);
let _ = get_event!(initiator, Event::FundingTransactionReadyForSigning);
let tx_complete = get_event_msg!(initiator, MessageSendEvent::SendTxComplete, node_id_acceptor);
acceptor.node.handle_tx_complete(node_id_initiator, &tx_complete);
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
check_added_monitors(acceptor, 1);
let tx_abort = if let MessageSendEvent::SendTxAbort { msg, .. } = &msg_events[0] {
msg
} else {
panic!("Unexpected event {:?}", msg_events[0]);
};
let update = if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[1] {
updates
} else {
panic!("Unexpected event {:?}", msg_events[1]);
};
initiator.node.handle_tx_abort(node_id_acceptor, tx_abort);
expect_splice_failed_events(
initiator,
&channel_id,
funding_contribution,
NegotiationFailureReason::CounterpartyAborted {
msg: UntrustedString(
"Total value of outputs exceeds total value of inputs".to_string(),
),
},
);
let tx_abort = get_event_msg!(initiator, MessageSendEvent::SendTxAbort, node_id_acceptor);
acceptor.node.handle_tx_abort(node_id_initiator, &tx_abort);
initiator.node.handle_update_add_htlc(node_id_acceptor, &update.update_add_htlcs[0]);
do_commitment_signed_dance(initiator, acceptor, &update.commitment_signed, false, false);
}
#[test]
fn free_holding_cell_on_tx_signatures_quiescence_exit() {
do_test_free_holding_cell_on_tx_signatures_quiescence_exit(true);
do_test_free_holding_cell_on_tx_signatures_quiescence_exit(false);
}
#[cfg(test)]
fn do_test_free_holding_cell_on_tx_signatures_quiescence_exit(update_from_initiator: bool) {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let config = test_default_channel_config();
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
if !update_from_initiator {
send_payment(initiator, &[acceptor], 2_000_000);
provide_utxo_reserves(&nodes, 2, Amount::ONE_BTC);
}
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
if update_from_initiator {
negotiate_splice_tx(initiator, acceptor, channel_id, initiator_contribution);
} else {
let acceptor_contribution =
initiate_splice_in(acceptor, initiator, channel_id, Amount::from_sat(200_000));
let stfu_initiator =
get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
let stfu_acceptor = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
acceptor.node.handle_stfu(node_id_initiator, &stfu_initiator);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
initiator.node.handle_stfu(node_id_acceptor, &stfu_acceptor);
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();
complete_interactive_funding_negotiation_for_both(
initiator,
acceptor,
channel_id,
initiator_contribution,
Some(acceptor_contribution),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
}
let (update_sender, update_recipient) =
if update_from_initiator { (initiator, acceptor) } else { (acceptor, initiator) };
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(update_sender, update_recipient, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
update_sender.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
assert!(update_sender.node.get_and_clear_pending_msg_events().is_empty());
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
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();
} else {
unreachable!();
}
let update = get_htlc_update_msgs(initiator, &node_id_acceptor);
acceptor.node.handle_commitment_signed(node_id_initiator, &update.commitment_signed[0]);
if !update_from_initiator {
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
let event = get_event!(acceptor, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
let partially_signed_tx = acceptor.wallet_source.sign_tx(unsigned_transaction).unwrap();
acceptor
.node
.funding_transaction_signed(&channel_id, &counterparty_node_id, partially_signed_tx)
.unwrap();
} else {
unreachable!();
}
}
check_added_monitors(acceptor, 1);
let acceptor_msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(
acceptor_msg_events.len(),
if update_from_initiator { 2 } else { 1 },
"{acceptor_msg_events:?}"
);
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &acceptor_msg_events[0] {
assert!(updates.update_add_htlcs.is_empty());
assert_eq!(updates.commitment_signed.len(), 1);
let commitment_signed = &updates.commitment_signed[0];
initiator.node.handle_commitment_signed(node_id_acceptor, commitment_signed);
check_added_monitors(&initiator, 1);
} else {
panic!("Unexpected event {:?}", &acceptor_msg_events[0]);
}
let expect_tx_signatures_then_htlc_update = |msg_events: &[MessageSendEvent]| match msg_events {
[MessageSendEvent::SendTxSignatures { .. }, MessageSendEvent::UpdateHTLCs { updates, .. }] =>
{
assert_eq!(updates.update_add_htlcs.len(), 1);
assert_eq!(updates.commitment_signed.len(), 2);
},
_ => panic!("Unexpected events {msg_events:?}"),
};
if update_from_initiator {
if let MessageSendEvent::SendTxSignatures { node_id, ref msg } = &acceptor_msg_events[1] {
assert_eq!(*node_id, node_id_initiator);
initiator.node.handle_tx_signatures(node_id_acceptor, msg);
} else {
panic!("Unexpected event {:?}", &acceptor_msg_events[1]);
}
let initiator_msg_events = initiator.node.get_and_clear_pending_msg_events();
check_added_monitors(initiator, 1); expect_tx_signatures_then_htlc_update(&initiator_msg_events);
} else {
let initiator_tx_signatures =
get_event_msg!(initiator, MessageSendEvent::SendTxSignatures, node_id_acceptor);
acceptor.node.handle_tx_signatures(node_id_initiator, &initiator_tx_signatures);
let acceptor_msg_events = acceptor.node.get_and_clear_pending_msg_events();
check_added_monitors(acceptor, 1); expect_tx_signatures_then_htlc_update(&acceptor_msg_events);
}
initiator.node.peer_disconnected(node_id_acceptor);
acceptor.node.peer_disconnected(node_id_initiator);
let mut reconnect_args = ReconnectArgs::new(initiator, acceptor);
if update_from_initiator {
reconnect_args.send_announcement_sigs = (true, true);
reconnect_args.send_interactive_tx_sigs = (false, true);
reconnect_args.pending_htlc_adds = (0, 1);
} else {
reconnect_args.send_interactive_tx_sigs = (true, false);
reconnect_args.pending_htlc_adds = (1, 0);
}
reconnect_nodes(reconnect_args);
expect_splice_pending_event(initiator, &node_id_acceptor);
if !update_from_initiator {
expect_splice_pending_event(acceptor, &node_id_initiator);
}
}
#[test]
fn fail_splice_on_channel_close() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let _node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let splice_in_amount = initial_channel_capacity / 2;
let _ = initiate_splice_in(initiator, acceptor, channel_id, Amount::from_sat(splice_in_amount));
let _ = complete_splice_handshake(initiator, acceptor);
let _tx_add_input =
get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
initiator
.node
.force_close_broadcasting_latest_txn(&channel_id, &node_id_acceptor, "test".to_owned())
.unwrap();
handle_bump_events(initiator, true, 0);
check_closed_events(
&nodes[0],
&[ExpectedCloseEvent {
channel_id: Some(channel_id),
discard_funding: true,
splice_failed: true,
channel_funding_txo: None,
user_channel_id: Some(42),
..Default::default()
}],
);
check_closed_broadcast(&nodes[0], 1, true);
check_added_monitors(&nodes[0], 1);
}
#[test]
fn fail_quiescent_action_on_channel_close() {
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);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let _node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
let splice_in_amount = initial_channel_capacity / 2;
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let _ = initiate_splice_in(initiator, acceptor, channel_id, Amount::from_sat(splice_in_amount));
let _stfu_init = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
initiator
.node
.force_close_broadcasting_latest_txn(&channel_id, &node_id_acceptor, "test".to_owned())
.unwrap();
handle_bump_events(initiator, true, 0);
check_closed_events(
&nodes[0],
&[ExpectedCloseEvent {
channel_id: Some(channel_id),
discard_funding: true,
splice_failed: true,
channel_funding_txo: None,
user_channel_id: Some(42),
..Default::default()
}],
);
check_closed_broadcast(&nodes[0], 1, true);
check_added_monitors(&nodes[0], 1);
}
#[test]
fn abandon_splice_quiescent_action_on_shutdown() {
do_abandon_splice_quiescent_action_on_shutdown(true, false);
do_abandon_splice_quiescent_action_on_shutdown(false, false);
do_abandon_splice_quiescent_action_on_shutdown(true, true);
do_abandon_splice_quiescent_action_on_shutdown(false, true);
}
#[cfg(test)]
fn do_abandon_splice_quiescent_action_on_shutdown(local_shutdown: bool, pending_splice: bool) {
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);
provide_utxo_reserves(&nodes, 1, Amount::ONE_BTC);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_capacity = 100_000;
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
if pending_splice {
let funding_contribution = do_initiate_splice_in(
&nodes[0],
&nodes[1],
channel_id,
Amount::from_sat(initial_channel_capacity / 2),
);
let (_splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
}
let payment_amount = 1_000_000;
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(&nodes[0], &nodes[1], payment_amount);
let onion = RecipientOnionFields::secret_only(payment_secret, payment_amount);
let payment_id = PaymentId(payment_hash.0);
nodes[0].node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
let update = get_htlc_update_msgs(&nodes[0], &node_id_1);
check_added_monitors(&nodes[0], 1);
nodes[1].node.handle_update_add_htlc(node_id_0, &update.update_add_htlcs[0]);
nodes[1].node.handle_commitment_signed_batch_test(node_id_0, &update.commitment_signed);
check_added_monitors(&nodes[1], 1);
let (revoke_and_ack, _) = get_revoke_commit_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_revoke_and_ack(node_id_1, &revoke_and_ack);
check_added_monitors(&nodes[0], 1);
let splice_in_amount =
if pending_splice { initial_channel_capacity / 4 } else { initial_channel_capacity / 2 };
let splice_out_output = if pending_splice {
let script_pubkey = nodes[1].wallet_source.get_change_script().unwrap();
Some(TxOut { value: Amount::from_sat(1_000), script_pubkey })
} else {
None
};
let funding_contribution = if let Some(ref output) = splice_out_output {
initiate_splice_in_and_out(
&nodes[0],
&nodes[1],
channel_id,
Amount::from_sat(splice_in_amount),
vec![output.clone()],
)
} else {
initiate_splice_in(&nodes[0], &nodes[1], channel_id, Amount::from_sat(splice_in_amount))
};
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let (closer_node, closee_node) =
if local_shutdown { (&nodes[0], &nodes[1]) } else { (&nodes[1], &nodes[0]) };
let closer_node_id = closer_node.node.get_our_node_id();
let closee_node_id = closee_node.node.get_our_node_id();
closer_node.node.close_channel(&channel_id, &closee_node_id).unwrap();
let shutdown = get_event_msg!(closer_node, MessageSendEvent::SendShutdown, closee_node_id);
closee_node.node.handle_shutdown(closer_node_id, &shutdown);
if pending_splice {
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2, "{events:?}");
match &events[0] {
Event::DiscardFunding {
funding_info: FundingInfo::Contribution { inputs, outputs },
..
} => {
assert!(inputs.is_empty(), "Expected empty inputs (filtered), got {:?}", inputs);
let expected_outputs: Vec<_> =
splice_out_output.into_iter().map(|output| output.script_pubkey).collect();
assert_eq!(*outputs, expected_outputs);
},
other => panic!("Expected DiscardFunding with Contribution, got {:?}", other),
}
match &events[1] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, contribution, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::ChannelClosing);
assert!(contribution.is_some());
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
} else {
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::ChannelClosing,
);
}
let _ = get_event_msg!(closee_node, MessageSendEvent::SendShutdown, closer_node_id);
}
#[cfg(test)]
fn do_test_splice_with_inflight_htlc_forward_and_resolution(expire_scid_pre_forward: bool) {
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let mut config = test_default_channel_config();
config.channel_config.cltv_expiry_delta = CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY as u16 * 2;
let node_chanmgrs = create_node_chanmgrs(
3,
&node_cfgs,
&[Some(config.clone()), Some(config.clone()), Some(config)],
);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let node_id_2 = nodes[2].node.get_our_node_id();
let (_, _, channel_id_0_1, _) = create_announced_chan_between_nodes(&nodes, 0, 1);
let (chan_upd_1_2, _, channel_id_1_2, _) = create_announced_chan_between_nodes(&nodes, 1, 2);
let node_max_height =
nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32;
connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1);
connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1);
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
let payment_amount = 1_000_000;
let payment_params =
PaymentParameters::from_node_id(node_id_2, CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY * 2)
.with_bolt11_features(nodes[2].node.bolt11_invoice_features())
.unwrap();
let route_params =
RouteParameters::from_payment_params_and_value(payment_params, payment_amount);
let route = get_route(&nodes[0], &route_params).unwrap();
let (_, payment_hash, payment_secret) =
get_payment_preimage_hash(&nodes[2], Some(payment_amount), None);
let onion = RecipientOnionFields::secret_only(payment_secret, payment_amount);
let id = PaymentId(payment_hash.0);
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, onion, id).unwrap();
check_added_monitors(&nodes[0], 1);
let update_add_0_1 = get_htlc_update_msgs(&nodes[0], &node_id_1);
nodes[1].node.handle_update_add_htlc(node_id_0, &update_add_0_1.update_add_htlcs[0]);
let commitment = &update_add_0_1.commitment_signed;
do_commitment_signed_dance(&nodes[1], &nodes[0], commitment, false, false);
assert!(nodes[1].node.needs_pending_htlc_processing());
let outputs_0_1 = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id_0_1, outputs_0_1).unwrap();
let (splice_tx_0_1, _) = splice_channel(&nodes[0], &nodes[1], channel_id_0_1, contribution);
for node in &nodes {
mine_transaction(node, &splice_tx_0_1);
}
let outputs_1_2 = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}];
let contribution =
initiate_splice_out(&nodes[1], &nodes[2], channel_id_1_2, outputs_1_2).unwrap();
let (splice_tx_1_2, _) = splice_channel(&nodes[1], &nodes[2], channel_id_1_2, contribution);
for node in &nodes {
mine_transaction(node, &splice_tx_1_2);
}
for node in &nodes {
connect_blocks(node, ANTI_REORG_DELAY - 2);
}
let splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
lock_splice(&nodes[0], &nodes[1], &splice_locked, false, &[]);
for node in &nodes {
connect_blocks(node, 1);
}
let splice_locked = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceLocked, node_id_2);
lock_splice(&nodes[1], &nodes[2], &splice_locked, false, &[]);
if expire_scid_pre_forward {
for node in &nodes {
connect_blocks(node, CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY);
}
let fail_type = HTLCHandlingFailureType::InvalidForward {
requested_forward_scid: chan_upd_1_2.contents.short_channel_id,
};
expect_htlc_forwarding_fails(&nodes[1], &[fail_type]);
check_added_monitors(&nodes[1], 1);
let update_fail_1_0 = get_htlc_update_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_update_fail_htlc(node_id_1, &update_fail_1_0.update_fail_htlcs[0]);
let commitment = &update_fail_1_0.commitment_signed;
do_commitment_signed_dance(&nodes[0], &nodes[1], commitment, false, false);
let conditions = PaymentFailedConditions::new();
expect_payment_failed_conditions(&nodes[0], payment_hash, false, conditions);
} else {
nodes[1].node.process_pending_htlc_forwards();
check_added_monitors(&nodes[1], 1);
let update_add_1_2 = get_htlc_update_msgs(&nodes[1], &node_id_2);
nodes[2].node.handle_update_add_htlc(node_id_1, &update_add_1_2.update_add_htlcs[0]);
let commitment = &update_add_1_2.commitment_signed;
do_commitment_signed_dance(&nodes[2], &nodes[1], commitment, false, false);
assert!(nodes[2].node.needs_pending_htlc_processing());
nodes[2].node.process_pending_htlc_forwards();
expect_payment_claimable!(&nodes[2], payment_hash, payment_secret, payment_amount);
nodes[2].node.fail_htlc_backwards(&payment_hash);
let fail_type = HTLCHandlingFailureType::Receive { payment_hash };
expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[2], &[fail_type]);
check_added_monitors(&nodes[2], 1);
let update_fail_1_2 = get_htlc_update_msgs(&nodes[2], &node_id_1);
nodes[1].node.handle_update_fail_htlc(node_id_2, &update_fail_1_2.update_fail_htlcs[0]);
let commitment = &update_fail_1_2.commitment_signed;
do_commitment_signed_dance(&nodes[1], &nodes[2], commitment, false, false);
let fail_type = HTLCHandlingFailureType::Forward {
node_id: Some(node_id_2),
channel_id: channel_id_1_2,
};
expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[1], &[fail_type]);
check_added_monitors(&nodes[1], 1);
let update_fail_0_1 = get_htlc_update_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_update_fail_htlc(node_id_1, &update_fail_0_1.update_fail_htlcs[0]);
let commitment = &update_fail_0_1.commitment_signed;
do_commitment_signed_dance(&nodes[0], &nodes[1], commitment, false, false);
let conditions = PaymentFailedConditions::new();
expect_payment_failed_conditions(&nodes[0], payment_hash, true, conditions);
}
}
#[test]
fn test_splice_with_inflight_htlc_forward_and_resolution() {
do_test_splice_with_inflight_htlc_forward_and_resolution(true);
do_test_splice_with_inflight_htlc_forward_and_resolution(false);
}
#[test]
fn test_splice_buffer_commitment_signed_until_funding_tx_signed() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let acceptor_commit_sig = get_htlc_update_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_commitment_signed(node_id_1, &acceptor_commit_sig.commitment_signed[0]);
check_added_monitors(&nodes[0], 0);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
if let Event::FundingTransactionReadyForSigning {
channel_id: event_channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = signing_event
{
assert_eq!(event_channel_id, channel_id);
assert_eq!(counterparty_node_id, node_id_1);
let partially_signed_tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0]
.node
.funding_transaction_signed(&channel_id, &node_id_1, partially_signed_tx)
.unwrap();
} else {
panic!("Expected FundingTransactionReadyForSigning event");
}
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
let initiator_commit_sig =
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
updates.commitment_signed[0].clone()
} else {
panic!("Expected UpdateHTLCs message");
};
check_added_monitors(&nodes[0], 1);
nodes[1].node.handle_commitment_signed(node_id_0, &initiator_commit_sig);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { ref msg, .. } = &msg_events[0] {
nodes[0].node.handle_tx_signatures(node_id_1, msg);
} else {
panic!("Expected SendTxSignatures message");
}
check_added_monitors(&nodes[1], 1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { ref msg, .. } = &msg_events[0] {
nodes[1].node.handle_tx_signatures(node_id_0, msg);
} else {
panic!("Expected SendTxSignatures message");
}
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let splice_tx = {
let mut txn_0 = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn_0.len(), 1);
let txn_1 = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(txn_0, txn_1);
txn_0.remove(0)
};
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn test_splice_buffer_invalid_commitment_signed_closes_channel() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let mut acceptor_commit_sig = get_htlc_update_msgs(&nodes[1], &node_id_0);
let original_sig = acceptor_commit_sig.commitment_signed[0].signature;
let mut sig_bytes = original_sig.serialize_compact();
sig_bytes[0] ^= 0x01; acceptor_commit_sig.commitment_signed[0].signature =
Signature::from_compact(&sig_bytes).unwrap();
nodes[0].node.handle_commitment_signed(node_id_1, &acceptor_commit_sig.commitment_signed[0]);
check_added_monitors(&nodes[0], 0);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
if let Event::FundingTransactionReadyForSigning {
channel_id: event_channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = signing_event
{
assert_eq!(event_channel_id, channel_id);
assert_eq!(counterparty_node_id, node_id_1);
let partially_signed_tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0]
.node
.funding_transaction_signed(&channel_id, &node_id_1, partially_signed_tx)
.unwrap();
} else {
panic!("Expected FundingTransactionReadyForSigning event");
}
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 3, "{msg_events:?}");
match &msg_events[0] {
MessageSendEvent::UpdateHTLCs { ref updates, .. } => {
assert!(!updates.commitment_signed.is_empty());
},
_ => panic!("Expected UpdateHTLCs message, got {:?}", msg_events[0]),
}
match &msg_events[1] {
MessageSendEvent::HandleError {
action: msgs::ErrorAction::SendErrorMessage { ref msg },
..
} => {
assert!(msg.data.contains("Invalid commitment tx signature from peer"));
},
_ => panic!("Expected HandleError with SendErrorMessage, got {:?}", msg_events[1]),
}
match &msg_events[2] {
MessageSendEvent::BroadcastChannelUpdate { ref msg, .. } => {
assert_eq!(msg.contents.channel_flags & 2, 2);
},
_ => panic!("Expected BroadcastChannelUpdate, got {:?}", msg_events[2]),
}
let err = "Invalid commitment tx signature from peer".to_owned();
let reason = ClosureReason::ProcessingError { err };
check_closed_events(
&nodes[0],
&[ExpectedCloseEvent::from_id_reason(channel_id, false, reason)],
);
check_added_monitors(&nodes[0], 1);
}
#[test]
fn test_splice_waits_for_initial_commitment_monitor_update_before_releasing_tx_signatures() {
do_splice_waits_for_initial_commitment_monitor_update_before_releasing_tx_signatures(false);
do_splice_waits_for_initial_commitment_monitor_update_before_releasing_tx_signatures(true);
}
#[cfg(test)]
fn do_splice_waits_for_initial_commitment_monitor_update_before_releasing_tx_signatures(
complete_update_while_disconnected: bool,
) {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let initiator_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap();
negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
let signing_event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id: event_channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = signing_event
{
assert_eq!(event_channel_id, channel_id);
assert_eq!(counterparty_node_id, node_id_1);
let partially_signed_tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[0]
.node
.funding_transaction_signed(&channel_id, &node_id_1, partially_signed_tx)
.unwrap();
} else {
panic!("Expected FundingTransactionReadyForSigning event");
}
let initiator_commit_sig = get_htlc_update_msgs(&nodes[0], &node_id_1);
nodes[1].node.handle_commitment_signed(node_id_0, &initiator_commit_sig.commitment_signed[0]);
check_added_monitors(&nodes[1], 1);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
let counterparty_commit_sig =
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
updates.commitment_signed[0].clone()
} else {
panic!("Expected UpdateHTLCs message");
};
let counterparty_tx_signatures =
if let MessageSendEvent::SendTxSignatures { ref msg, .. } = &msg_events[1] {
msg.clone()
} else {
panic!("Expected SendTxSignatures message");
};
nodes[0].node.handle_commitment_signed(node_id_1, &counterparty_commit_sig);
check_added_monitors(&nodes[0], 1);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_tx_signatures(node_id_1, &counterparty_tx_signatures);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
if complete_update_while_disconnected {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
}
nodes[0].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
if !complete_update_while_disconnected {
let initiator_tx_signatures =
get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
nodes[1].node.handle_tx_signatures(node_id_0, &initiator_tx_signatures);
}
expect_splice_pending_event(&nodes[0], &node_id_1);
if !complete_update_while_disconnected {
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
}
#[test]
fn test_monitor_restore_sends_tx_signatures_before_splice_locked() {
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_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let (funding_tx, channel_id) =
open_zero_conf_channel_with_value(&nodes[0], &nodes[1], None, initial_channel_value_sat, 0);
mine_transaction(&nodes[0], &funding_tx);
mine_transaction(&nodes[1], &funding_tx);
let prev_funding_txid = funding_tx.compute_txid();
provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000));
let splice_in_sat = Amount::from_sat(50_000);
let funding_contribution = initiate_splice_in(&nodes[1], &nodes[0], channel_id, splice_in_sat);
negotiate_splice_tx(&nodes[1], &nodes[0], channel_id, funding_contribution);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let event = get_event!(nodes[1], Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
let partially_signed_tx = nodes[1].wallet_source.sign_tx(unsigned_transaction).unwrap();
nodes[1]
.node
.funding_transaction_signed(&channel_id, &node_id_0, partially_signed_tx)
.unwrap();
} else {
panic!();
}
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
nodes[0].node.handle_commitment_signed(node_id_1, &updates.commitment_signed[0]);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
check_added_monitors(&nodes[0], 1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[1].node.handle_commitment_signed(node_id_0, &updates.commitment_signed[0]);
check_added_monitors(&nodes[1], 1);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[1] {
nodes[1].node.handle_tx_signatures(node_id_0, msg);
check_added_monitors(&nodes[1], 0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].tx_broadcaster.txn_broadcast().is_empty());
} else {
panic!("Unexpected event {:?}", msg_events[1]);
}
nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
expect_splice_pending_event(&nodes[1], &node_id_0);
let txn = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1, "{txn:?}");
let splice_tx = txn[0].clone();
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[0] {
nodes[0].node.handle_tx_signatures(node_id_1, msg);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
if let MessageSendEvent::SendSpliceLocked { msg, .. } = &msg_events[1] {
nodes[0].node.handle_splice_locked(node_id_1, msg);
} else {
panic!("Unexpected event {:?}", msg_events[1]);
}
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(!txn.is_empty());
assert!(txn.iter().all(|tx| *tx == splice_tx), "{txn:?}");
expect_channel_ready_event(&nodes[0], &node_id_1);
check_added_monitors(&nodes[0], 1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::SendSpliceLocked { ref msg, .. } = msg_events[0] {
nodes[1].node.handle_splice_locked(node_id_0, msg);
} else {
panic!("Unexpected event {:?}", msg_events[0]);
}
expect_channel_ready_event(&nodes[1], &node_id_0);
check_added_monitors(&nodes[1], 1);
let txn = nodes[1].tx_broadcaster.txn_broadcast();
assert!(txn.iter().all(|tx| *tx == splice_tx), "{txn:?}");
nodes[0].chain_source.remove_watched_by_txid(prev_funding_txid);
nodes[1].chain_source.remove_watched_by_txid(prev_funding_txid);
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn retransmit_completed_tx_signatures_during_monitor_update_after_reestablish() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let config = test_default_channel_config();
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
negotiate_splice_tx(initiator, acceptor, channel_id, contribution);
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(initiator, acceptor, 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
initiator.node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} = event
{
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();
} else {
unreachable!();
}
let update = get_htlc_update_msgs(initiator, &node_id_acceptor);
acceptor.node.handle_commitment_signed(node_id_initiator, &update.commitment_signed[0]);
check_added_monitors(&acceptor, 1);
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = &msg_events[0] {
let commitment_signed = &updates.commitment_signed[0];
initiator.node.handle_commitment_signed(node_id_acceptor, commitment_signed);
check_added_monitors(&initiator, 1);
} else {
panic!("Unexpected event {:?}", &msg_events[0]);
}
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
let splice_txid =
if let MessageSendEvent::SendTxSignatures { node_id, ref msg } = &msg_events[1] {
assert_eq!(*node_id, node_id_initiator);
initiator.node.handle_tx_signatures(node_id_acceptor, msg);
msg.tx_hash
} else {
panic!("Unexpected event {:?}", &msg_events[1]);
};
check_added_monitors(&initiator, 1);
expect_splice_pending_event(initiator, &node_id_acceptor);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::SendTxSignatures { node_id, ref msg } = &msg_events[0] {
assert_eq!(*node_id, node_id_acceptor);
assert_eq!(msg.tx_hash, splice_txid);
} else {
panic!("Unexpected event {:?}", &msg_events[0]);
}
initiator.node.peer_disconnected(node_id_acceptor);
acceptor.node.peer_disconnected(node_id_initiator);
let mut reconnect_args = ReconnectArgs::new(acceptor, initiator);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_args.send_interactive_tx_sigs = (true, false);
reconnect_nodes(reconnect_args);
check_added_monitors(acceptor, 0);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
initiator.chain_monitor.complete_sole_pending_chan_update(&channel_id);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
let msg_events = initiator.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
acceptor.node.handle_update_add_htlc(node_id_initiator, &updates.update_add_htlcs[0]);
do_commitment_signed_dance(acceptor, initiator, &updates.commitment_signed, false, false);
} else {
panic!("Unexpected event {:?}", &msg_events[0]);
}
}
#[test]
fn test_splice_balance_falls_below_reserve() {
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let initial_channel_value_sat = 100_000;
let push_msat = 10_000_000;
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
0,
1,
initial_channel_value_sat,
push_msat,
);
let _ = provide_anchor_reserves(&nodes);
let (preimage_0_to_1, _hash_0_to_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let (preimage_1_to_0, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 8_000_000);
let initiator_contribution =
initiate_splice_in(&nodes[0], &nodes[1], channel_id, Amount::from_sat(200_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);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1);
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0);
let _ = send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
}
#[test]
fn test_funding_contributed_counterparty_not_found() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 1, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
let fake_node_id =
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
assert_eq!(
nodes[0].node.funding_contributed(
&channel_id,
&fake_node_id,
funding_contribution.clone(),
None
),
Err(APIError::no_such_peer(&fake_node_id)),
);
expect_discard_funding_event(&nodes[0], &channel_id, funding_contribution);
}
#[test]
fn test_funding_contributed_channel_not_found() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 1, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
let fake_channel_id = ChannelId::from_bytes([42; 32]);
assert_eq!(
nodes[0].node.funding_contributed(
&fake_channel_id,
&node_id_1,
funding_contribution.clone(),
None
),
Err(APIError::no_such_channel_for_peer(&fake_channel_id, &node_id_1)),
);
expect_discard_funding_event(&nodes[0], &fake_channel_id, funding_contribution);
}
#[test]
fn test_funding_contributed_splice_already_pending() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 2, splice_in_amount * 2);
let first_splice_out = TxOut {
value: Amount::from_sat(5_000),
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_raw_hash(Hash::all_zeros())),
};
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let first_contribution = funding_template
.with_prior_contribution(feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(splice_in_amount)
.unwrap()
.add_output(first_splice_out.clone())
.build()
.unwrap();
let second_splice_out = TxOut {
value: Amount::from_sat(6_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
};
nodes[0].wallet_source.clear_utxos();
provide_utxo_reserves(&nodes, 1, splice_in_amount * 3);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let second_contribution = funding_template
.without_prior_contribution(feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(splice_in_amount)
.unwrap()
.add_output(second_splice_out.clone())
.build()
.unwrap();
assert_eq!(
first_contribution.change_output().map(|output| &output.script_pubkey),
second_contribution.change_output().map(|output| &output.script_pubkey),
);
let change_script = first_contribution.change_output().unwrap().script_pubkey.clone();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, first_contribution, None).unwrap();
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let (expected_inputs, mut expected_outputs) =
second_contribution.clone().into_contributed_inputs_and_outputs();
expected_outputs.retain(|output| *output != change_script);
assert_eq!(
nodes[0].node.funding_contributed(&channel_id, &node_id_1, second_contribution, None),
Err(APIError::APIMisuseError {
err: format!("Channel {} already has a pending funding contribution", channel_id),
})
);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match &events[0] {
Event::DiscardFunding { channel_id: event_channel_id, funding_info } => {
assert_eq!(event_channel_id, &channel_id);
if let FundingInfo::Contribution { inputs, outputs } = funding_info {
assert_eq!(*inputs, expected_inputs);
assert_eq!(*outputs, expected_outputs);
} else {
panic!("Expected FundingInfo::Contribution");
}
},
_ => panic!("Expected DiscardFunding event"),
}
}
#[test]
fn test_funding_contributed_duplicate_contribution_no_event() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 1, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None).unwrap();
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
assert_eq!(
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution, None),
Err(APIError::APIMisuseError {
err: format!("Duplicate funding contribution for channel {}", channel_id),
})
);
let events = nodes[0].node.get_and_clear_pending_events();
assert!(events.is_empty(), "Expected no events for duplicate contribution, got {:?}", events);
}
#[test]
fn test_funding_contributed_active_funding_negotiation() {
do_test_funding_contributed_active_funding_negotiation(0); do_test_funding_contributed_active_funding_negotiation(1); do_test_funding_contributed_active_funding_negotiation(2); }
#[cfg(test)]
fn do_test_funding_contributed_active_funding_negotiation(state: u8) {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 2, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let first_contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
nodes[0].wallet_source.clear_utxos();
provide_utxo_reserves(&nodes, 1, splice_in_amount * 3);
let splice_out_output = TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
};
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let second_contribution = funding_template
.without_prior_contribution(feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(splice_in_amount)
.unwrap()
.add_outputs(vec![splice_out_output.clone()])
.build()
.unwrap();
assert_eq!(
first_contribution.change_output().map(|output| &output.script_pubkey),
second_contribution.change_output().map(|output| &output.script_pubkey),
);
let change_script = first_contribution.change_output().unwrap().script_pubkey.clone();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, first_contribution.clone(), None)
.unwrap();
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
if state >= 1 {
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
if state == 2 {
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
first_contribution.clone(),
new_funding_script,
);
let _ = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
}
}
let (expected_inputs, mut expected_outputs) =
second_contribution.clone().into_contributed_inputs_and_outputs();
expected_outputs.retain(|output| *output != change_script);
assert_eq!(
nodes[0].node.funding_contributed(&channel_id, &node_id_1, second_contribution, None),
Err(APIError::APIMisuseError {
err: format!("Channel {} already has a pending funding contribution", channel_id),
})
);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1, "{events:?}");
match &events[0] {
Event::DiscardFunding { channel_id: event_channel_id, funding_info } => {
assert_eq!(*event_channel_id, channel_id);
if let FundingInfo::Contribution { inputs, outputs } = funding_info {
assert_eq!(*inputs, expected_inputs);
assert_eq!(*outputs, vec![splice_out_output.script_pubkey]);
} else {
panic!("Expected FundingInfo::Contribution");
}
},
_ => panic!("Expected DiscardFunding event, got {:?}", events[1]),
}
assert_eq!(
nodes[0].node.funding_contributed(&channel_id, &node_id_1, first_contribution, None),
Err(APIError::APIMisuseError {
err: format!("Duplicate funding contribution for channel {}", channel_id),
})
);
let events = nodes[0].node.get_and_clear_pending_events();
assert!(events.is_empty(), "Expected no events for duplicate contribution, got {:?}", events);
if state == 1 {
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
assert!(matches!(msg_events[0], MessageSendEvent::SendTxAddInput { .. }));
}
if state == 2 {
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
assert!(matches!(msg_events[0], MessageSendEvent::UpdateHTLCs { .. }));
}
}
#[test]
fn test_funding_contributed_channel_shutdown() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 1, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
nodes[0].node.close_channel(&channel_id, &node_id_1).unwrap();
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, node_id_1);
assert_eq!(
nodes[0].node.funding_contributed(
&channel_id,
&node_id_1,
funding_contribution.clone(),
None
),
Err(APIError::APIMisuseError {
err: format!("Channel {} cannot accept funding contribution", channel_id),
})
);
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::ChannelClosing,
);
}
#[test]
fn test_funding_contributed_unfunded_channel() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, funded_channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let unfunded_channel_id = exchange_open_accept_chan(&nodes[0], &nodes[1], 50_000, 0);
let _ = get_event!(nodes[0], Event::FundingGenerationReady);
let splice_in_amount = Amount::from_sat(20_000);
provide_utxo_reserves(&nodes, 1, splice_in_amount * 2);
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&funded_channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution =
funding_template.splice_in_sync(splice_in_amount, feerate, FeeRate::MAX, &wallet).unwrap();
assert_eq!(
nodes[0].node.funding_contributed(
&unfunded_channel_id,
&node_id_1,
funding_contribution.clone(),
None
),
Err(APIError::APIMisuseError {
err: format!(
"Channel with id {} not expecting funding contribution",
unfunded_channel_id
),
})
);
expect_discard_funding_event(&nodes[0], &unfunded_channel_id, funding_contribution);
}
#[test]
fn test_splice_pending_htlcs() {
let mut config = test_default_channel_config();
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
do_test_splice_pending_htlcs(config);
let mut config = test_default_channel_config();
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
do_test_splice_pending_htlcs(config);
let mut config = test_default_channel_config();
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
do_test_splice_pending_htlcs(config);
}
#[cfg(test)]
fn do_test_splice_pending_htlcs(config: UserConfig) {
let anchors_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
let initial_channel_value = Amount::from_sat(100_000);
let push_amount = Amount::from_sat(10_000);
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, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
&nodes,
0,
1,
initial_channel_value.to_sat(),
push_amount.to_sat() * 1000,
);
let details = &nodes[0].node.list_channels()[0];
let channel_type = details.channel_type.clone().unwrap();
let feerate_per_kw = details.feerate_sat_per_1000_weight.unwrap();
let spike_multiple = if channel_type == ChannelTypeFeatures::only_static_remote_key() {
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
} else {
1
};
let spiked_feerate = spike_multiple * feerate_per_kw;
let (preimage_1_to_0_a, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 2_000_000);
let (preimage_1_to_0_b, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 2_000_000);
let (preimage_1_to_0_c, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 2_000_000);
let (preimage_0_to_1_a, _hash_0_to_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 40_000_000);
let (preimage_0_to_1_b, _hash_0_to_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 40_000_000);
let splice_out_dance = |initiator: usize,
acceptor: usize,
splice_out: Amount,
splice_out_incl_fees: Amount,
post_splice_reserve: Amount|
-> FundingContribution {
let initiator = &nodes[initiator];
let acceptor = &nodes[acceptor];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let script_pubkey = initiator.wallet_source.get_change_script().unwrap();
let outputs = vec![TxOut { value: splice_out + Amount::ONE_SAT, script_pubkey }];
assert!(matches!(
build_splice_out_contribution(initiator, acceptor, channel_id, outputs),
Err(FundingContributionError::InvalidSpliceValue),
));
let script_pubkey = initiator.wallet_source.get_change_script().unwrap();
let outputs = vec![TxOut { value: splice_out, script_pubkey }];
let contribution =
initiate_splice_out(initiator, acceptor, channel_id, outputs.clone()).unwrap();
assert_eq!(contribution.net_value(), -splice_out_incl_fees.to_signed().unwrap());
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 mut splice_init =
get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
splice_init.funding_contribution_satoshis -= 1;
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
let msg = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
assert_eq!(msg.channel_id, channel_id);
let cannot_be_spliced_out = format!(
"Their post-splice channel balance {} is smaller than our selected v2 reserve {}",
post_splice_reserve - Amount::ONE_SAT,
post_splice_reserve
);
assert_eq!(tx_abort_data(&msg), format!("Invalid contribution: {cannot_be_spliced_out}"));
acceptor.node.peer_disconnected(node_id_initiator);
initiator.node.peer_disconnected(node_id_acceptor);
let reconnect_args = ReconnectArgs::new(initiator, acceptor);
reconnect_nodes(reconnect_args);
expect_splice_failed_events(
initiator,
&channel_id,
contribution,
NegotiationFailureReason::PeerDisconnected,
);
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
assert_eq!(contribution.net_value(), -splice_out_incl_fees.to_signed().unwrap());
contribution
};
let (preimage_1_to_0_d, node_1_splice_out_incl_fees) = {
let debit_htlcs = Amount::from_sat(2_000 * 3);
let balance = push_amount - debit_htlcs;
let estimated_fees = Amount::from_sat(183);
let splice_out = Amount::from_sat(1000);
let splice_out_incl_fees = splice_out + estimated_fees;
let post_splice_reserve = (initial_channel_value - splice_out_incl_fees) / 100;
let pre_splice_balance = post_splice_reserve + splice_out_incl_fees;
let amount_msat = (balance - pre_splice_balance).to_sat() * 1000;
let (preimage_1_to_0_d, ..) = route_payment(&nodes[1], &[&nodes[0]], amount_msat);
let contribution =
splice_out_dance(1, 0, splice_out, splice_out_incl_fees, post_splice_reserve);
let _new_funding_script = complete_splice_handshake(&nodes[1], &nodes[0]);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_nodes(reconnect_args);
expect_splice_failed_events(
&nodes[1],
&channel_id,
contribution,
NegotiationFailureReason::PeerDisconnected,
);
let details = &nodes[1].node.list_channels()[0];
let expected_outbound_htlc_max =
(pre_splice_balance.to_sat() - details.unspendable_punishment_reserve.unwrap()) * 1000;
assert_eq!(details.next_outbound_htlc_limit_msat, expected_outbound_htlc_max);
(preimage_1_to_0_d, splice_out_incl_fees)
};
let preimage_0_to_1_d = {
let debit_htlcs = Amount::from_sat(40_000 * 2);
let debit_anchors =
if channel_type == anchors_features { Amount::from_sat(330 * 2) } else { Amount::ZERO };
let balance = initial_channel_value - push_amount - debit_htlcs - debit_anchors;
let estimated_fees = Amount::from_sat(183);
let splice_out = Amount::from_sat(1000);
let splice_out_incl_fees = splice_out + estimated_fees;
let post_splice_reserve = (initial_channel_value - splice_out_incl_fees) / 100;
let htlc_count = 6 + 1 + 1;
let commit_tx_fee = Amount::from_sat(chan_utils::commit_tx_fee_sat(
spiked_feerate,
htlc_count,
&channel_type,
));
let pre_splice_balance = post_splice_reserve + commit_tx_fee + splice_out_incl_fees;
let amount_msat = (balance - pre_splice_balance).to_sat() * 1000;
let (preimage_0_to_1_d, ..) = route_payment(&nodes[0], &[&nodes[1]], amount_msat);
let contribution =
splice_out_dance(0, 1, splice_out, splice_out_incl_fees, post_splice_reserve);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
assert_eq!(nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat, 0);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
let outbound_htlc_max = nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat;
assert_eq!(outbound_htlc_max, node_1_splice_out_incl_fees.to_sat() * 1000);
let _ = send_payment(&nodes[1], &[&nodes[0]], node_1_splice_out_incl_fees.to_sat() * 1000);
assert_eq!(nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat, 0);
let previous_balance = chan_utils::commit_tx_fee_sat(spiked_feerate, 8, &channel_type);
let claimed_htlc = node_1_splice_out_incl_fees.to_sat();
let commit_tx_fee = chan_utils::commit_tx_fee_sat(spiked_feerate, 9, &channel_type);
let new_balance = previous_balance + claimed_htlc - commit_tx_fee;
let outbound_htlc_max = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
assert_eq!(outbound_htlc_max, new_balance * 1000);
preimage_0_to_1_d
};
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0_a);
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0_b);
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0_c);
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0_d);
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1_a);
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1_b);
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1_d);
let _ = send_payment(&nodes[0], &[&nodes[1]], 2_000 * 1000);
let _ = send_payment(&nodes[1], &[&nodes[0]], 2_000 * 1000);
}
pub fn reenter_quiescence<'a, 'b, 'c>(
node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_id: &ChannelId,
) {
let node_id_a = node_a.node.get_our_node_id();
let node_id_b = node_b.node.get_our_node_id();
node_a.node.maybe_propose_quiescence(&node_id_b, channel_id).unwrap();
let stfu_a = get_event_msg!(node_a, MessageSendEvent::SendStfu, node_id_b);
node_b.node.handle_stfu(node_id_a, &stfu_a);
let stfu_b = get_event_msg!(node_b, MessageSendEvent::SendStfu, node_id_a);
node_a.node.handle_stfu(node_id_b, &stfu_b);
}
#[test]
fn test_splice_acceptor_disconnect_emits_events() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let node_0_funding_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let _node_1_funding_contribution =
do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
expect_splice_failed_events(
&nodes[0],
&channel_id,
node_0_funding_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2, "{events:?}");
match &events[0] {
Event::DiscardFunding {
funding_info: FundingInfo::Contribution { inputs, outputs },
..
} => {
assert!(!inputs.is_empty(), "Expected acceptor inputs, got empty");
assert!(!outputs.is_empty(), "Expected acceptor outputs, got empty");
},
other => panic!("Expected DiscardFunding with Contribution, got {:?}", other),
}
match &events[1] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, contribution, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
assert!(contribution.is_some());
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
}
#[test]
fn test_splice_rbf_acceptor_basic() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (first_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution,
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx,
ANTI_REORG_DELAY - 1,
&[first_splice_tx.compute_txid()],
);
assert!(result.node_a_discarded.is_empty());
assert!(result.node_b_discarded.is_empty());
}
#[test]
fn test_splice_rbf_discard_unique_contribution() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let splice_out_output = TxOut {
value: Amount::from_sat(5_000),
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
};
let funding_contribution = do_initiate_splice_in_and_out(
&nodes[0],
&nodes[1],
channel_id,
added_value,
vec![splice_out_output.clone()],
);
let round_0_inputs: Vec<_> = funding_contribution.contributed_inputs().collect();
assert!(!round_0_inputs.is_empty());
let (first_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
nodes[0].wallet_source.clear_utxos();
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution = funding_template
.without_prior_contribution(rbf_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(added_value)
.unwrap()
.build()
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, funding_contribution.clone(), None)
.unwrap();
let round_1_inputs: Vec<_> = funding_contribution.contributed_inputs().collect();
assert_ne!(round_0_inputs, round_1_inputs, "Rounds must use different UTXOs");
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution,
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx,
ANTI_REORG_DELAY - 1,
&[first_splice_tx.compute_txid()],
);
assert_eq!(result.node_a_discarded.len(), 1);
let (ref inputs, ref outputs) = result.node_a_discarded[0];
assert_eq!(*inputs, round_0_inputs);
assert_eq!(*outputs, vec![splice_out_output.script_pubkey]);
assert!(result.node_b_discarded.is_empty());
}
#[test]
fn test_splice_rbf_at_high_feerate() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (first_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let high_feerate = FeeRate::from_sat_per_kwu(1000);
let contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, high_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script.clone(),
);
let (rbf_tx_1, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = {
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
funding_template.min_rbf_feerate().unwrap()
};
let contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script,
);
let (_, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).replacing(rbf_tx_1.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
#[test]
fn test_splice_rbf_insufficient_feerate() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let same_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let min_rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(min_rbf_feerate, expected_floor);
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(funding_template
.splice_in_sync(added_value, same_feerate, FeeRate::MAX, &wallet)
.is_err());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(funding_template
.splice_in_sync(added_value, min_rbf_feerate, FeeRate::MAX, &wallet)
.is_ok());
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, min_rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let mut tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
tx_init_rbf.feerate_sat_per_1000_weight = FEERATE_FLOOR_SATS_PER_KW;
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
let (route, payment_hash, _payment_preimage, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], 1_000_000);
let onion = RecipientOnionFields::secret_only(payment_secret, 1_000_000);
let payment_id = PaymentId(payment_hash.0);
nodes[0].node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1, "{events:?}");
assert!(
matches!(&events[0], Event::SpliceNegotiationFailed { channel_id: cid, .. } if *cid == channel_id)
);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
let tx_abort_echo = match &msg_events[0] {
MessageSendEvent::SendTxAbort { msg, .. } => msg.clone(),
other => panic!("Expected SendTxAbort, got {:?}", other),
};
match &msg_events[1] {
MessageSendEvent::UpdateHTLCs { updates, .. } => {
assert_eq!(updates.update_add_htlcs.len(), 1);
},
other => panic!("Expected UpdateHTLCs, got {:?}", other),
}
check_added_monitors(&nodes[0], 1);
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[1] {
nodes[1].node.handle_update_add_htlc(node_id_0, &updates.update_add_htlcs[0]);
do_commitment_signed_dance(&nodes[1], &nodes[0], &updates.commitment_signed, false, false);
} else {
unreachable!();
}
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort_echo);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, min_rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let mut tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
let rbf_feerate_25_24 = ((FEERATE_FLOOR_SATS_PER_KW as u64) * 25 / 24) as u32;
tx_init_rbf.feerate_sat_per_1000_weight = rbf_feerate_25_24;
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort);
let tx_abort_echo = get_event_msg!(nodes[0], MessageSendEvent::SendTxAbort, node_id_1);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
assert!(
matches!(&events[0], Event::SpliceNegotiationFailed { channel_id: cid, .. } if *cid == channel_id)
);
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort_echo);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, min_rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert_eq!(tx_init_rbf.feerate_sat_per_1000_weight, FEERATE_FLOOR_SATS_PER_KW + 25);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let _tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
}
#[test]
fn test_splice_rbf_insufficient_feerate_high() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let high_feerate = FeeRate::from_sat_per_kwu(1000);
let contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, high_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script,
);
let (_, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).replacing(splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
provide_utxo_reserves(&nodes, 2, added_value * 2);
let min_rbf_feerate = FeeRate::from_sat_per_kwu(1041);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, min_rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let mut tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
tx_init_rbf.feerate_sat_per_1000_weight = 1025;
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort);
let tx_abort_echo = get_event_msg!(nodes[0], MessageSendEvent::SendTxAbort, node_id_1);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
assert!(
matches!(&events[0], Event::SpliceNegotiationFailed { channel_id: cid, .. } if *cid == channel_id)
);
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort_echo);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, min_rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert_eq!(tx_init_rbf.feerate_sat_per_1000_weight, 1041);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let _tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
}
#[test]
fn test_splice_rbf_no_pending_splice() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
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);
reenter_quiescence(&nodes[0], &nodes[1], &channel_id);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: 500,
funding_output_contribution: Some(50_000),
};
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(
tx_abort_data(&tx_abort),
"Rejecting RBF attempt: No pending splice available to RBF"
);
}
#[test]
fn test_splice_rbf_active_negotiation() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let _new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: 500,
funding_output_contribution: Some(added_value.to_sat() as i64),
};
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
nodes[0].node.get_and_clear_pending_msg_events();
}
#[test]
fn test_splice_rbf_after_splice_locked() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert!(msg_events.is_empty(), "Expected no messages, got {:?}", msg_events);
reenter_quiescence(&nodes[0], &nodes[1], &channel_id);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: 500,
funding_output_contribution: Some(added_value.to_sat() as i64),
};
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort_data(&tx_abort), "Rejecting RBF attempt: Already received splice_locked");
}
#[test]
fn test_splice_rbf_stfu_after_splice_locked() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution = funding_template
.without_prior_contribution(rbf_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(added_value)
.unwrap()
.build()
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, funding_contribution.clone(), None)
.unwrap();
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let _splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
match &msg_events[0] {
MessageSendEvent::HandleError { action, .. } => {
assert_eq!(
*action,
msgs::ErrorAction::DisconnectPeerWithWarning {
msg: msgs::WarningMessage {
channel_id,
data: format!(
"Channel {} already sent splice_locked, cannot RBF",
channel_id,
),
},
}
);
},
_ => panic!("Expected HandleError, got {:?}", msg_events[0]),
}
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2, "{events:?}");
match &events[0] {
Event::DiscardFunding {
funding_info: FundingInfo::Contribution { inputs, outputs },
..
} => {
assert!(!inputs.is_empty());
assert!(outputs.is_empty());
},
other => panic!("Expected DiscardFunding, got {:?}", other),
}
match &events[1] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::CannotInitiateRbf);
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
}
#[test]
fn test_splice_zeroconf_no_rbf_feerate() {
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_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let (funding_tx, channel_id) =
open_zero_conf_channel_with_value(&nodes[0], &nodes[1], None, initial_channel_value_sat, 0);
mine_transaction(&nodes[0], &funding_tx);
mine_transaction(&nodes[1], &funding_tx);
let added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let _funding_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let _new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
let funding_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
assert!(funding_template.min_rbf_feerate().is_none());
nodes[0].node.get_and_clear_pending_msg_events();
}
#[test]
fn test_splice_rbf_zeroconf_rejected() {
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_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let (funding_tx, channel_id) =
open_zero_conf_channel_with_value(&nodes[0], &nodes[1], None, initial_channel_value_sat, 0);
mine_transaction(&nodes[0], &funding_tx);
mine_transaction(&nodes[1], &funding_tx);
reenter_quiescence(&nodes[0], &nodes[1], &channel_id);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: 500,
funding_output_contribution: Some(50_000),
};
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(
tx_abort_data(&tx_abort),
format!("Rejecting RBF attempt: Channel {} has option_zeroconf, cannot RBF", channel_id)
);
}
#[test]
fn test_splice_rbf_not_quiescence_initiator() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let _funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let _tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: 500,
funding_output_contribution: Some(added_value.to_sat() as i64),
};
nodes[0].node.handle_tx_init_rbf(node_id_1, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[0], MessageSendEvent::SendTxAbort, node_id_1);
assert_eq!(tx_abort.channel_id, channel_id);
}
#[test]
fn test_splice_rbf_both_contribute_tiebreak() {
let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let feerate = FeeRate::from_sat_per_kwu(min_rbf_feerate);
let added_value = Amount::from_sat(50_000);
do_test_splice_rbf_tiebreak(feerate, feerate, added_value, true);
}
#[test]
fn test_splice_rbf_tiebreak_higher_feerate() {
let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(min_rbf_feerate * 3),
FeeRate::from_sat_per_kwu(min_rbf_feerate),
Amount::from_sat(50_000),
true,
);
}
#[test]
fn test_splice_rbf_tiebreak_lower_feerate() {
let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(min_rbf_feerate),
FeeRate::from_sat_per_kwu(min_rbf_feerate * 3),
Amount::from_sat(50_000),
false,
);
}
#[test]
fn test_splice_rbf_tiebreak_feerate_too_high() {
let min_rbf_feerate = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
do_test_splice_rbf_tiebreak(
FeeRate::from_sat_per_kwu(20_000),
FeeRate::from_sat_per_kwu(min_rbf_feerate),
Amount::from_sat(95_000),
false,
);
}
pub fn do_test_splice_rbf_tiebreak(
rbf_feerate_0: FeeRate, rbf_feerate_1: FeeRate, node_1_splice_value: Amount,
expect_acceptor_contributes: bool,
) {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (first_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let node_0_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate_0);
let node_1_funding_contribution = do_initiate_splice_in_at_feerate(
&nodes[1],
&nodes[0],
channel_id,
node_1_splice_value,
rbf_feerate_1,
);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
assert!(stfu_0.initiator);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
assert!(stfu_1.initiator);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert_eq!(tx_init_rbf.channel_id, channel_id);
assert_eq!(tx_init_rbf.feerate_sat_per_1000_weight, rbf_feerate_0.to_sat_per_kwu() as u32);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
assert_eq!(tx_ack_rbf.channel_id, channel_id);
let acceptor_contributes = tx_ack_rbf.funding_output_contribution.is_some();
assert_eq!(
acceptor_contributes, expect_acceptor_contributes,
"Expected acceptor contribution: {}, got: {}",
expect_acceptor_contributes, acceptor_contributes,
);
nodes[0].node.handle_tx_ack_rbf(node_id_1, &tx_ack_rbf);
if acceptor_contributes {
let node_0_change = node_0_funding_contribution
.change_output()
.expect("splice-in should have a change output")
.clone();
let node_1_change = node_1_funding_contribution
.change_output()
.expect("splice-in should have a change output")
.clone();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution),
tx_ack_rbf.funding_output_contribution.unwrap(),
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.with_acceptor_contribution()
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
let initiator_change_in_tx = rbf_tx
.output
.iter()
.find(|o| o.script_pubkey == node_0_change.script_pubkey)
.expect("Initiator's change output should be in the RBF transaction");
assert_eq!(
initiator_change_in_tx.value, node_0_change.value,
"Initiator's change output should remain unchanged",
);
let acceptor_change_in_tx = rbf_tx
.output
.iter()
.find(|o| o.script_pubkey == node_1_change.script_pubkey)
.expect("Acceptor's change output should be in the RBF transaction");
if rbf_feerate_0 <= rbf_feerate_1 {
assert!(
acceptor_change_in_tx.value > node_1_change.value,
"Acceptor's change should increase when initiator feerate ({}) <= acceptor \
feerate ({}): adjusted {} vs original {}",
rbf_feerate_0.to_sat_per_kwu(),
rbf_feerate_1.to_sat_per_kwu(),
acceptor_change_in_tx.value,
node_1_change.value,
);
} else {
assert!(
acceptor_change_in_tx.value < node_1_change.value,
"Acceptor's change should decrease when initiator feerate ({}) > acceptor \
feerate ({}): adjusted {} vs original {}",
rbf_feerate_0.to_sat_per_kwu(),
rbf_feerate_1.to_sat_per_kwu(),
acceptor_change_in_tx.value,
node_1_change.value,
);
}
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx,
ANTI_REORG_DELAY - 1,
&[first_splice_tx.compute_txid()],
);
assert!(result.node_a_discarded.is_empty());
assert!(result.node_b_discarded.is_empty());
} else {
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
None,
0,
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx,
ANTI_REORG_DELAY - 1,
&[first_splice_tx.compute_txid()],
);
let stfu_1 = if let Some(MessageSendEvent::SendStfu { msg, .. }) = result.stfu {
msg
} else {
panic!("Expected SendStfu from node 1");
};
assert!(stfu_1.initiator);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let splice_init = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceInit, node_id_0);
nodes[0].node.handle_splice_init(node_id_1, &splice_init);
let splice_ack = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceAck, node_id_1);
nodes[1].node.handle_splice_ack(node_id_0, &splice_ack);
let new_funding_script_2 = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation(
&nodes[1],
&nodes[0],
channel_id,
node_1_funding_contribution,
new_funding_script_2,
);
let (new_splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[1], &nodes[0]));
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[1], &node_id_0);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
mine_transaction(&nodes[1], &new_splice_tx);
mine_transaction(&nodes[0], &new_splice_tx);
lock_splice_after_blocks(&nodes[1], &nodes[0], ANTI_REORG_DELAY - 1);
}
}
#[test]
fn test_splice_rbf_tiebreak_feerate_too_high_rejected() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_first_splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let high_feerate = FeeRate::from_sat_per_kwu(100_000);
let min_rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let min_rbf_feerate = FeeRate::from_sat_per_kwu(min_rbf_feerate_sat_per_kwu);
let node_1_max_feerate = FeeRate::from_sat_per_kwu(3_000);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution = funding_template_0
.splice_in_sync(added_value, high_feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution = funding_template_1
.splice_in_sync(added_value, min_rbf_feerate, node_1_max_feerate, &wallet_1)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert_eq!(tx_init_rbf.feerate_sat_per_1000_weight, high_feerate.to_sat_per_kwu() as u32);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2);
match &msg_events[0] {
MessageSendEvent::SendTxAbort { node_id, msg } => {
assert_eq!(*node_id, node_id_0);
assert_eq!(msg.channel_id, channel_id);
},
_ => panic!("Expected SendTxAbort, got {:?}", msg_events[0]),
};
match &msg_events[1] {
MessageSendEvent::SendStfu { node_id, .. } => {
assert_eq!(*node_id, node_id_0);
},
_ => panic!("Expected SendStfu, got {:?}", msg_events[1]),
};
}
#[test]
fn test_splice_rbf_acceptor_recontributes() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution =
funding_template_0.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_0).unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution =
funding_template_1.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_1).unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution.clone()),
splice_ack.funding_contribution_satoshis,
new_funding_script.clone(),
);
let (first_splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let tx_ack_rbf = complete_rbf_handshake(&nodes[0], &nodes[1]);
assert!(
tx_ack_rbf.funding_output_contribution.is_some(),
"Acceptor should re-contribute via our_prior_contribution"
);
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
rbf_funding_contribution,
Some(node_1_funding_contribution),
tx_ack_rbf.funding_output_contribution.unwrap(),
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.with_acceptor_contribution()
.replacing(first_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx,
ANTI_REORG_DELAY - 1,
&[first_splice_tx.compute_txid()],
);
assert!(result.node_a_discarded.is_empty());
assert!(result.node_b_discarded.is_empty());
}
#[test]
fn test_splice_rbf_after_counterparty_rbf_aborted() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution =
funding_template_0.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_0).unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution =
funding_template_1.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_1).unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
let (_first_splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let _rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let tx_ack_rbf = complete_rbf_handshake(&nodes[0], &nodes[1]);
assert!(tx_ack_rbf.funding_output_contribution.is_some());
nodes[0].node.get_and_clear_pending_msg_events();
let tx_abort = msgs::TxAbort { channel_id, data: vec![] };
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert!(!msg_events.is_empty());
let tx_abort_echo = match &msg_events[0] {
MessageSendEvent::SendTxAbort { msg, .. } => msg.clone(),
other => panic!("Expected SendTxAbort, got {:?}", other),
};
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort_echo);
nodes[0].node.get_and_clear_pending_msg_events();
nodes[0].node.get_and_clear_pending_events();
nodes[1].node.get_and_clear_pending_events();
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
assert!(funding_template.min_rbf_feerate().is_some());
assert_eq!(
funding_template.prior_contribution().unwrap().feerate(),
feerate,
"Prior contribution should have the original feerate, not the RBF-adjusted one",
);
let wallet = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let rbf_contribution =
funding_template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet);
assert!(rbf_contribution.is_ok());
}
#[test]
fn test_splice_rbf_recontributes_feerate_too_high() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_funding_contribution = funding_template_0
.splice_in_sync(Amount::from_sat(50_000), floor_feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, node_0_funding_contribution.clone(), None)
.unwrap();
let node_1_added_value = Amount::from_sat(95_000);
let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_funding_contribution = funding_template_1
.splice_in_sync(node_1_added_value, floor_feerate, FeeRate::MAX, &wallet_1)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_funding_contribution.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution),
splice_ack.funding_contribution_satoshis,
new_funding_script.clone(),
);
let (_first_splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let high_feerate = FeeRate::from_sat_per_kwu(20_000);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let rbf_funding_contribution = funding_template
.splice_in_sync(Amount::from_sat(50_000), high_feerate, FeeRate::MAX, &wallet)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, rbf_funding_contribution.clone(), None)
.unwrap();
let stfu_a = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_a);
let stfu_b = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_b);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
assert_eq!(tx_init_rbf.feerate_sat_per_1000_weight, high_feerate.to_sat_per_kwu() as u32);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_abort = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
assert_eq!(tx_abort.channel_id, channel_id);
}
#[test]
fn test_splice_rbf_sequential() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx_0, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let feerate_1_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25; let feerate_2_sat_per_kwu = feerate_1_sat_per_kwu + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_1 = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
let funding_contribution_1 =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate_1);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution_1,
new_funding_script.clone(),
);
let (splice_tx_1, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(splice_tx_0.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_2 = FeeRate::from_sat_per_kwu(feerate_2_sat_per_kwu);
let funding_contribution_2 =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate_2);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution_2,
new_funding_script.clone(),
);
let (rbf_tx_final, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(splice_tx_1.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let splice_tx_0_txid = splice_tx_0.compute_txid();
let splice_tx_1_txid = splice_tx_1.compute_txid();
let result = lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx_final,
ANTI_REORG_DELAY - 1,
&[splice_tx_0_txid, splice_tx_1_txid],
);
assert!(result.node_a_discarded.is_empty());
assert!(result.node_b_discarded.is_empty());
}
#[test]
fn test_splice_rbf_amends_prior_net_positive_contribution_request() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let initial_added_value = Amount::from_sat(100_000);
let half_added_value = Amount::from_sat(initial_added_value.to_sat() / 2);
provide_utxo_reserves(&nodes, 1, Amount::from_sat(250_000));
let initial_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, initial_added_value);
let (initial_inputs, _) = initial_contribution.clone().into_contributed_inputs_and_outputs();
let (splice_tx_0, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, initial_contribution.clone());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let first_output = TxOut {
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_raw_hash(Hash::all_zeros())),
};
let second_output = TxOut {
value: Amount::from_sat(15_000),
script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::all_zeros()),
};
let run_rbf_round = |contribution: FundingContribution, replaced_txid: Txid| {
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None)
.unwrap();
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script.clone(),
);
let (tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).replacing(replaced_txid),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
tx
};
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(funding_template.prior_contribution().unwrap().outputs().is_empty());
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_1 = funding_template
.splice_out(vec![first_output.clone(), second_output.clone()], rbf_feerate, FeeRate::MAX)
.unwrap();
let (inputs_1, _) = contribution_1.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_1, initial_inputs);
assert_eq!(contribution_1.outputs(), &[first_output.clone(), second_output.clone()]);
assert_eq!(contribution_1.net_value(), initial_contribution.net_value());
assert!(
contribution_1.change_output().unwrap().value
< initial_contribution.change_output().unwrap().value
);
let splice_tx_1 = run_rbf_round(contribution_1.clone(), splice_tx_0.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_1.outputs());
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_2 = funding_template
.with_prior_contribution(rbf_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.remove_value(half_added_value)
.unwrap()
.build()
.unwrap();
let (inputs_2, _) = contribution_2.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_2, initial_inputs);
assert_eq!(contribution_2.outputs(), contribution_1.outputs());
assert!(contribution_2.net_value() < contribution_1.net_value());
let splice_tx_2 = run_rbf_round(contribution_2.clone(), splice_tx_1.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_2.outputs());
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_3 = funding_template
.with_prior_contribution(rbf_feerate, FeeRate::MAX)
.remove_outputs(&first_output.script_pubkey)
.build()
.unwrap();
let (inputs_3, _) = contribution_3.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_3, initial_inputs);
assert_eq!(contribution_3.outputs(), std::slice::from_ref(&second_output));
assert_eq!(contribution_3.net_value(), contribution_2.net_value());
assert!(
contribution_3.change_output().unwrap().value
> contribution_2.change_output().unwrap().value
);
let splice_tx_3 = run_rbf_round(contribution_3.clone(), splice_tx_2.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_3.outputs());
let contribution_4 =
funding_template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet).unwrap();
let (inputs_4, _) = contribution_4.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_4, initial_inputs);
assert_eq!(contribution_4.outputs(), contribution_3.outputs());
assert_eq!(contribution_4.net_value(), contribution_3.net_value());
assert!(
contribution_4.change_output().unwrap().value
< contribution_3.change_output().unwrap().value
);
let rbf_tx_final = run_rbf_round(contribution_4, splice_tx_3.compute_txid());
lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx_final,
ANTI_REORG_DELAY - 1,
&[
splice_tx_0.compute_txid(),
splice_tx_1.compute_txid(),
splice_tx_2.compute_txid(),
splice_tx_3.compute_txid(),
],
);
}
#[test]
fn test_splice_rbf_amends_prior_net_negative_contribution_request() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let first_output = TxOut {
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_raw_hash(Hash::all_zeros())),
};
let second_output = TxOut {
value: Amount::from_sat(15_000),
script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::all_zeros()),
};
let initial_contribution =
initiate_splice_out(&nodes[0], &nodes[1], channel_id, vec![first_output.clone()]).unwrap();
let (initial_inputs, _) = initial_contribution.clone().into_contributed_inputs_and_outputs();
assert!(initial_inputs.is_empty());
let (splice_tx_0, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, initial_contribution.clone());
let manual_input_pair_tx = provide_utxo_reserves(&nodes, 2, Amount::from_sat(20_000));
let manual_input_single_tx = provide_utxo_reserves(&nodes, 1, Amount::from_sat(10_000));
let manual_input_0 = ConfirmedUtxo::new_p2wpkh(manual_input_pair_tx.clone(), 0).unwrap();
let manual_input_1 = ConfirmedUtxo::new_p2wpkh(manual_input_pair_tx, 1).unwrap();
let manual_input_2 = ConfirmedUtxo::new_p2wpkh(manual_input_single_tx, 0).unwrap();
let run_rbf_round = |contribution: FundingContribution, replaced_txid: Txid| {
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None)
.unwrap();
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script.clone(),
);
let (tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).replacing(replaced_txid),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
tx
};
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(
funding_template.prior_contribution().unwrap().outputs(),
std::slice::from_ref(&first_output),
);
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_1 = funding_template
.splice_out(vec![second_output.clone()], rbf_feerate, FeeRate::MAX)
.unwrap();
let (inputs_1, _) = contribution_1.clone().into_contributed_inputs_and_outputs();
assert!(inputs_1.is_empty());
assert_eq!(contribution_1.outputs(), &[first_output.clone(), second_output.clone()]);
assert!(contribution_1.net_value() < initial_contribution.net_value());
let splice_tx_1 = run_rbf_round(contribution_1.clone(), splice_tx_0.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_1.outputs());
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_2 = funding_template
.with_prior_contribution(rbf_feerate, FeeRate::MAX)
.remove_outputs(&first_output.script_pubkey)
.build()
.unwrap();
let (inputs_2, _) = contribution_2.clone().into_contributed_inputs_and_outputs();
assert!(inputs_2.is_empty());
assert_eq!(contribution_2.outputs(), std::slice::from_ref(&second_output));
assert!(contribution_2.net_value() > contribution_1.net_value());
let splice_tx_2 = run_rbf_round(contribution_2.clone(), splice_tx_1.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_2.outputs());
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_3 = funding_template
.with_prior_contribution(rbf_feerate, FeeRate::MAX)
.add_inputs(vec![manual_input_0.clone(), manual_input_1.clone()])
.unwrap()
.build()
.unwrap();
let (inputs_3, _) = contribution_3.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_3, vec![manual_input_0.utxo.outpoint, manual_input_1.utxo.outpoint],);
assert_eq!(contribution_3.outputs(), contribution_2.outputs());
assert!(contribution_3.net_value() > SignedAmount::ZERO);
assert!(contribution_3.change_output().is_none());
let splice_tx_3 = run_rbf_round(contribution_3.clone(), splice_tx_2.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_3.outputs());
let prior_inputs = funding_template
.prior_contribution()
.unwrap()
.clone()
.into_contributed_inputs_and_outputs()
.0;
assert_eq!(prior_inputs, vec![manual_input_0.utxo.outpoint, manual_input_1.utxo.outpoint],);
let rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let contribution_4 = funding_template
.with_prior_contribution(rbf_feerate, FeeRate::MAX)
.add_input(manual_input_2.clone())
.unwrap()
.remove_input(&manual_input_0.utxo.outpoint)
.unwrap()
.remove_input(&manual_input_1.utxo.outpoint)
.unwrap()
.build()
.unwrap();
let (inputs_4, _) = contribution_4.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_4, vec![manual_input_2.utxo.outpoint]);
assert_eq!(contribution_4.outputs(), contribution_3.outputs());
assert!(contribution_4.net_value() < SignedAmount::ZERO);
assert!(contribution_4.net_value() < contribution_3.net_value());
assert!(contribution_4.change_output().is_none());
let splice_tx_4 = run_rbf_round(contribution_4.clone(), splice_tx_3.compute_txid());
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert_eq!(funding_template.prior_contribution().unwrap().outputs(), contribution_4.outputs());
let contribution_5 =
funding_template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet).unwrap();
let (inputs_5, _) = contribution_5.clone().into_contributed_inputs_and_outputs();
assert_eq!(inputs_5, vec![manual_input_2.utxo.outpoint]);
assert_eq!(contribution_5.outputs(), contribution_4.outputs());
assert!(contribution_5.net_value() < SignedAmount::ZERO);
assert!(contribution_5.net_value() < contribution_4.net_value());
assert!(contribution_5.change_output().is_none());
let rbf_tx_final = run_rbf_round(contribution_5, splice_tx_4.compute_txid());
lock_rbf_splice_after_blocks(
&nodes[0],
&nodes[1],
&rbf_tx_final,
ANTI_REORG_DELAY - 1,
&[
splice_tx_0.compute_txid(),
splice_tx_1.compute_txid(),
splice_tx_2.compute_txid(),
splice_tx_3.compute_txid(),
splice_tx_4.compute_txid(),
],
);
}
#[test]
fn test_splice_rbf_acceptor_contributes_then_disconnects() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let node_0_funding_contribution =
do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let node_1_funding_contribution =
do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(splice_ack.funding_contribution_satoshis, 0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
node_0_funding_contribution,
Some(node_1_funding_contribution.clone()),
splice_ack.funding_contribution_satoshis,
new_funding_script.clone(),
);
let (_first_splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let _rbf_funding_contribution =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let tx_ack_rbf = complete_rbf_handshake(&nodes[0], &nodes[1]);
assert!(
tx_ack_rbf.funding_output_contribution.is_some(),
"Acceptor should re-contribute via prior contribution"
);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let _ = get_event!(&nodes[0], Event::SpliceNegotiationFailed);
let _ = get_event!(&nodes[1], Event::SpliceNegotiationFailed);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
}
#[test]
fn test_splice_rbf_disconnect_filters_prior_contributions() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx_0, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let feerate_1_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
let splice_out_output = TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
};
let _funding_contribution_1 = do_initiate_rbf_splice_in_and_out(
&nodes[0],
&nodes[1],
channel_id,
vec![splice_out_output.clone()],
rbf_feerate,
);
complete_rbf_handshake(&nodes[0], &nodes[1]);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2, "{events:?}");
match &events[0] {
Event::DiscardFunding {
funding_info: FundingInfo::Contribution { inputs, outputs },
..
} => {
assert!(inputs.is_empty(), "Expected empty inputs (filtered), got {:?}", inputs);
assert_eq!(*outputs, vec![splice_out_output.script_pubkey.clone()]);
},
other => panic!("Expected DiscardFunding with Contribution, got {:?}", other),
}
match &events[1] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, contribution, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
assert!(contribution.is_some());
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let rbf_feerate_2 = FeeRate::from_sat_per_kwu(feerate_1_sat_per_kwu);
let _funding_contribution_2 =
do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate_2);
complete_rbf_handshake(&nodes[0], &nodes[1]);
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1, "{events:?}");
match &events[0] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, contribution, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
assert!(contribution.is_some());
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
}
#[test]
fn test_splice_channel_with_pending_splice_includes_rbf_floor() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
{
let template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(template.min_rbf_feerate().is_none());
assert!(template.prior_contribution().is_none());
}
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(funding_template.min_rbf_feerate(), Some(expected_floor));
assert!(funding_template.prior_contribution().is_some());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(funding_template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet).is_ok());
}
#[test]
fn test_funding_contributed_adjusts_feerate_for_rbf() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 4, added_value * 2);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(funding_template.min_rbf_feerate().is_none());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution =
funding_template.splice_in_sync(added_value, floor_feerate, FeeRate::MAX, &wallet).unwrap();
let node_1_contribution = do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let (_first_splice_tx, _new_funding_script) =
splice_channel(&nodes[1], &nodes[0], channel_id, node_1_contribution);
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None).unwrap();
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu_resp = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_resp);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
let rbf_feerate = FeeRate::from_sat_per_kwu(tx_init_rbf.feerate_sat_per_1000_weight as u64);
let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert!(rbf_feerate >= expected_floor);
}
#[test]
fn test_funding_contributed_rbf_adjustment_exceeds_max_feerate() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 4, added_value * 2);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution = funding_template
.splice_in_sync(added_value, floor_feerate, floor_feerate, &wallet)
.unwrap();
let node_1_contribution = do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let (_splice_tx, _) = splice_channel(&nodes[1], &nodes[0], channel_id, node_1_contribution);
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution, None).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
mine_transaction(&nodes[0], &_splice_tx);
mine_transaction(&nodes[1], &_splice_tx);
let stfu = lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1).stfu;
let stfu = match stfu {
Some(MessageSendEvent::SendStfu { msg, .. }) => {
assert!(msg.initiator);
msg
},
other => panic!("Expected SendStfu, got {:?}", other),
};
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu_resp = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_resp);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
assert_eq!(splice_init.funding_feerate_per_kw, FEERATE_FLOOR_SATS_PER_KW);
}
#[test]
fn test_peer_initiated_stfu_skips_local_rbf_feerate_check() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 4, added_value * 2);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let node_0_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let node_0_contribution =
node_0_template.splice_in_sync(added_value, floor_feerate, floor_feerate, &wallet).unwrap();
let node_1_contribution = do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let (_first_splice_tx, _) =
splice_channel(&nodes[1], &nodes[0], channel_id, node_1_contribution);
nodes[0].node.funding_contributed(&channel_id, &node_id_1, node_0_contribution, None).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let min_rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let _node_1_rbf_contribution =
do_initiate_rbf_splice_in(&nodes[1], &nodes[0], channel_id, min_rbf_feerate);
let stfu_init = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
assert!(stfu_init.initiator);
nodes[0].node.handle_stfu(node_id_1, &stfu_init);
let stfu_response = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
assert!(!stfu_response.initiator);
}
#[test]
fn test_funding_contributed_rbf_adjustment_insufficient_budget() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 4, added_value * 2);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = TightBudgetWallet {
utxo_value: added_value + Amount::from_sat(3000),
change_value: Amount::from_sat(300),
};
let contribution =
funding_template.splice_in_sync(added_value, floor_feerate, FeeRate::MAX, &wallet).unwrap();
let high_feerate = FeeRate::from_sat_per_kwu(10_000);
let node_1_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let node_1_wallet = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let node_1_contribution = node_1_template
.splice_in_sync(added_value, high_feerate, FeeRate::MAX, &node_1_wallet)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, node_1_contribution.clone(), None)
.unwrap();
let (_splice_tx, _) = splice_channel(&nodes[1], &nodes[0], channel_id, node_1_contribution);
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution, None).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
mine_transaction(&nodes[0], &_splice_tx);
mine_transaction(&nodes[1], &_splice_tx);
let stfu = lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1).stfu;
let stfu = match stfu {
Some(MessageSendEvent::SendStfu { msg, .. }) => {
assert!(msg.initiator);
msg
},
other => panic!("Expected SendStfu, got {:?}", other),
};
nodes[1].node.handle_stfu(node_id_0, &stfu);
let stfu_resp = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_resp);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
assert_eq!(splice_init.funding_feerate_per_kw, FEERATE_FLOOR_SATS_PER_KW);
}
#[test]
fn test_prior_contribution_unadjusted_when_max_feerate_too_low() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let funding_contribution = funding_template
.splice_in_sync(added_value, floor_feerate, floor_feerate, &wallet)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, funding_contribution.clone(), None)
.unwrap();
let (_splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(funding_template.min_rbf_feerate().is_some());
assert!(funding_template.prior_contribution().is_some());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(funding_template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet).is_ok());
}
#[test]
fn test_splice_channel_during_negotiation_includes_rbf_feerate() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let _funding_contribution =
do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let stfu_init = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_init);
let stfu_ack = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_ack);
let splice_init = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceInit, node_id_0);
nodes[0].node.handle_splice_init(node_id_1, &splice_init);
let _splice_ack = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceAck, node_id_1);
let template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let expected_floor = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
assert_eq!(template.min_rbf_feerate(), Some(expected_floor));
assert!(template.prior_contribution().is_none());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(matches!(
template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet),
Err(FundingContributionError::NotRbfScenario)
));
}
#[test]
fn test_rbf_sync_returns_err_when_no_min_rbf_feerate() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
assert!(template.min_rbf_feerate().is_none());
assert!(template.prior_contribution().is_none());
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(matches!(
template.rbf_prior_contribution_sync(None, FeeRate::MAX, &wallet),
Err(crate::ln::funding::FundingContributionError::NotRbfScenario),
));
}
#[test]
fn test_rbf_sync_returns_err_when_max_feerate_below_min_rbf() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let min_rbf_feerate = funding_template.min_rbf_feerate().unwrap();
let too_low_feerate =
FeeRate::from_sat_per_kwu(min_rbf_feerate.to_sat_per_kwu().saturating_sub(1));
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
assert!(matches!(
funding_template.rbf_prior_contribution_sync(None, too_low_feerate, &wallet),
Err(crate::ln::funding::FundingContributionError::FeeRateExceedsMaximum { .. }),
));
}
#[test]
fn test_splice_revalidation_at_quiescence() {
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 _ = provide_anchor_reserves(&nodes);
let payment_1_msat = 20_000_000;
let (route_1, payment_hash_1, _, payment_secret_1) =
get_route_and_payment_hash!(nodes[0], nodes[1], payment_1_msat);
nodes[0]
.node
.send_payment_with_route(
route_1,
payment_hash_1,
RecipientOnionFields::secret_only(payment_secret_1, payment_1_msat),
PaymentId(payment_hash_1.0),
)
.unwrap();
check_added_monitors(&nodes[0], 1);
let payment_1_msgs = nodes[0].node.get_and_clear_pending_msg_events();
let outputs = vec![TxOut {
value: Amount::from_sat(70_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let contribution = funding_template.splice_out(outputs, feerate, FeeRate::MAX).unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution.clone(), None).unwrap();
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty(), "stfu should be delayed");
let payment_1_event = SendEvent::from_event(payment_1_msgs.into_iter().next().unwrap());
nodes[1].node.handle_update_add_htlc(node_id_0, &payment_1_event.msgs[0]);
nodes[1].node.handle_commitment_signed_batch_test(node_id_0, &payment_1_event.commitment_msg);
check_added_monitors(&nodes[1], 1);
let (raa, cs) = get_revoke_commit_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_revoke_and_ack(node_id_1, &raa);
check_added_monitors(&nodes[0], 1);
let payment_2_msat = 20_000_000;
let (route_2, payment_hash_2, _, payment_secret_2) =
get_route_and_payment_hash!(nodes[0], nodes[1], payment_2_msat);
nodes[0]
.node
.send_payment_with_route(
route_2,
payment_hash_2,
RecipientOnionFields::secret_only(payment_secret_2, payment_2_msat),
PaymentId(payment_hash_2.0),
)
.unwrap();
check_added_monitors(&nodes[0], 1);
let payment_2_msgs = nodes[0].node.get_and_clear_pending_msg_events();
nodes[0].node.handle_commitment_signed_batch_test(node_id_1, &cs);
check_added_monitors(&nodes[0], 1);
let raa_0 = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, node_id_1);
nodes[1].node.handle_revoke_and_ack(node_id_0, &raa_0);
check_added_monitors(&nodes[1], 1);
let payment_2_event = SendEvent::from_event(payment_2_msgs.into_iter().next().unwrap());
nodes[1].node.handle_update_add_htlc(node_id_0, &payment_2_event.msgs[0]);
nodes[1].node.handle_commitment_signed_batch_test(node_id_0, &payment_2_event.commitment_msg);
check_added_monitors(&nodes[1], 1);
let (raa_1b, cs_1b) = get_revoke_commit_msgs(&nodes[1], &node_id_0);
nodes[0].node.handle_revoke_and_ack(node_id_1, &raa_1b);
check_added_monitors(&nodes[0], 1);
nodes[0].node.handle_commitment_signed_batch_test(node_id_1, &cs_1b);
check_added_monitors(&nodes[0], 1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
let raa_0b = match &msg_events[0] {
MessageSendEvent::SendRevokeAndACK { msg, .. } => msg.clone(),
other => panic!("Expected SendRevokeAndACK, got {:?}", other),
};
let stfu_0 = match &msg_events[1] {
MessageSendEvent::SendStfu { msg, .. } => msg.clone(),
other => panic!("Expected SendStfu, got {:?}", other),
};
nodes[1].node.handle_revoke_and_ack(node_id_0, &raa_0b);
check_added_monitors(&nodes[1], 1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
assert!(matches!(msg_events[0], MessageSendEvent::HandleError { .. }));
expect_splice_failed_events(
&nodes[0],
&channel_id,
contribution,
NegotiationFailureReason::ContributionInvalid,
);
}
#[test]
fn test_splice_init_before_quiescence_sends_warning() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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);
nodes[0].node.maybe_propose_quiescence(&node_id_1, &channel_id).unwrap();
let _stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let funding_pubkey =
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
let splice_init = msgs::SpliceInit {
channel_id,
funding_contribution_satoshis: 50_000,
funding_feerate_per_kw: FEERATE_FLOOR_SATS_PER_KW,
locktime: 0,
funding_pubkey,
require_confirmed_inputs: None,
};
nodes[0].node.handle_splice_init(node_id_1, &splice_init);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
match &msg_events[0] {
MessageSendEvent::HandleError { node_id, .. } => assert_eq!(*node_id, node_id_1),
other => panic!("Expected HandleError, got {:?}", other),
}
}
#[test]
fn test_tx_init_rbf_before_quiescence_sends_warning() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (_splice_tx, _new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
nodes[0].node.maybe_propose_quiescence(&node_id_1, &channel_id).unwrap();
let _stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let tx_init_rbf = msgs::TxInitRbf {
channel_id,
locktime: 0,
feerate_sat_per_1000_weight: FEERATE_FLOOR_SATS_PER_KW + 25,
funding_output_contribution: Some(added_value.to_sat() as i64),
};
nodes[0].node.handle_tx_init_rbf(node_id_1, &tx_init_rbf);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
match &msg_events[0] {
MessageSendEvent::HandleError { node_id, .. } => assert_eq!(*node_id, node_id_1),
other => panic!("Expected HandleError, got {:?}", other),
}
nodes[0].node.get_and_clear_pending_events();
nodes[1].node.get_and_clear_pending_events();
}
#[test]
fn test_splice_rbf_rejects_low_feerate_after_several_attempts() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (mut prev_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let high_feerate = 10_000;
*chanmon_cfgs[1].fee_estimator.sat_per_kw.lock().unwrap() = high_feerate;
let mut prev_feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
for _ in 0..10 {
let feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate);
let contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(prev_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
prev_feerate = feerate;
prev_splice_tx = rbf_tx;
}
let next_feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(next_feerate);
let _contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
}
#[test]
fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
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);
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (mut prev_splice_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution);
let high_feerate = 10_000;
*chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap() = high_feerate;
let mut prev_feerate = FEERATE_FLOOR_SATS_PER_KW as u64;
for _ in 0..10 {
let feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(feerate);
let contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script.clone(),
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(prev_splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
prev_feerate = feerate;
prev_splice_tx = rbf_tx;
}
let next_feerate = prev_feerate + 25;
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(next_feerate);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution = funding_template
.without_prior_contribution(rbf_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(added_value)
.unwrap()
.build()
.unwrap();
let result = nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution, None);
assert!(result.is_err(), "Expected rejection for low feerate: {:?}", result);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1, "{events:?}");
match &events[0] {
Event::SpliceNegotiationFailed { channel_id: cid, reason, contribution, .. } => {
assert_eq!(*cid, channel_id);
assert_eq!(*reason, NegotiationFailureReason::FeeRateTooLow);
assert!(contribution.is_some());
},
other => panic!("Expected SpliceNegotiationFailed, got {:?}", other),
}
}
#[test]
fn test_no_disconnect_after_splice_completes() {
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);
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
funding_contribution,
new_funding_script,
);
let (_, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]));
assert!(splice_locked.is_none());
let _node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
}
let has_disconnect = |events: &[MessageSendEvent]| {
events.iter().any(|event| {
matches!(
event,
MessageSendEvent::HandleError {
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
..
}
)
})
};
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
}
#[test]
fn test_no_disconnect_after_splice_aborted() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
complete_splice_handshake(&nodes[0], &nodes[1]);
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
nodes[0].node.cancel_funding_contributed(&channel_id, &node_id_1).unwrap();
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::LocallyCanceled,
);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
let tx_abort = msg_events
.iter()
.find_map(|event| {
if let MessageSendEvent::SendTxAbort { msg, .. } = event {
Some(msg.clone())
} else {
None
}
})
.expect("Expected SendTxAbort");
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort);
let tx_abort_echo = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
nodes[1].node.get_and_clear_pending_events();
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort_echo);
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
}
let has_disconnect = |events: &[MessageSendEvent]| {
events.iter().any(|event| {
matches!(
event,
MessageSendEvent::HandleError {
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
..
}
)
})
};
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
}
#[test]
fn test_no_disconnect_after_quiescence_on_reconnect() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
complete_splice_handshake(&nodes[0], &nodes[1]);
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
nodes[0].node.timer_tick_occurred();
nodes[1].node.timer_tick_occurred();
}
let has_disconnect = |events: &[MessageSendEvent]| {
events.iter().any(|event| {
matches!(
event,
MessageSendEvent::HandleError {
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
..
}
)
})
};
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
}
#[test]
fn test_0reserve_splice() {
let mut config = test_default_channel_config();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone());
let _b = do_test_0reserve_splice_holder_validation(true, false, false, config.clone());
let _c = do_test_0reserve_splice_holder_validation(false, true, false, config.clone());
let _d = do_test_0reserve_splice_holder_validation(true, true, false, config.clone());
let _e = do_test_0reserve_splice_holder_validation(false, false, true, config.clone());
let _f = do_test_0reserve_splice_holder_validation(true, false, true, config.clone());
let _g = do_test_0reserve_splice_holder_validation(false, true, true, config.clone());
let _h = do_test_0reserve_splice_holder_validation(true, true, true, config.clone());
assert_eq!(a, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone());
let _b = do_test_0reserve_splice_holder_validation(true, false, false, config.clone());
let _c = do_test_0reserve_splice_holder_validation(false, true, false, config.clone());
let _d = do_test_0reserve_splice_holder_validation(true, true, false, config.clone());
let _e = do_test_0reserve_splice_holder_validation(false, false, true, config.clone());
let _f = do_test_0reserve_splice_holder_validation(true, false, true, config.clone());
let _g = do_test_0reserve_splice_holder_validation(false, true, true, config.clone());
let _h = do_test_0reserve_splice_holder_validation(true, true, true, config.clone());
assert_eq!(a, ChannelTypeFeatures::anchors_zero_fee_commitments());
let mut config = test_default_channel_config();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone());
let _b = do_test_0reserve_splice_counterparty_validation(true, false, false, config.clone());
let _c = do_test_0reserve_splice_counterparty_validation(false, true, false, config.clone());
let _d = do_test_0reserve_splice_counterparty_validation(true, true, false, config.clone());
let _e = do_test_0reserve_splice_counterparty_validation(false, false, true, config.clone());
let _f = do_test_0reserve_splice_counterparty_validation(true, false, true, config.clone());
let _g = do_test_0reserve_splice_counterparty_validation(false, true, true, config.clone());
let _h = do_test_0reserve_splice_counterparty_validation(true, true, true, config.clone());
assert_eq!(a, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone());
let _b = do_test_0reserve_splice_counterparty_validation(true, false, false, config.clone());
let _c = do_test_0reserve_splice_counterparty_validation(false, true, false, config.clone());
let _d = do_test_0reserve_splice_counterparty_validation(true, true, false, config.clone());
let _e = do_test_0reserve_splice_counterparty_validation(false, false, true, config.clone());
let _f = do_test_0reserve_splice_counterparty_validation(true, false, true, config.clone());
let _g = do_test_0reserve_splice_counterparty_validation(false, true, true, config.clone());
let _h = do_test_0reserve_splice_counterparty_validation(true, true, true, config.clone());
assert_eq!(a, ChannelTypeFeatures::anchors_zero_fee_commitments());
}
#[cfg(test)]
fn do_test_0reserve_splice_holder_validation(
splice_passes: bool, counterparty_has_output: bool, node_0_is_initiator: bool,
mut config: UserConfig,
) -> ChannelTypeFeatures {
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs =
create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config.clone())]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let _node_id_0 = nodes[0].node.get_our_node_id();
let _node_id_1 = nodes[1].node.get_our_node_id();
let channel_value_sat = 100_000;
let dust_limit_satoshis = 546;
let (channel_id, _tx) =
setup_0reserve_no_outputs_channels(&nodes, channel_value_sat, dust_limit_satoshis);
let details = &nodes[0].node.list_channels()[0];
let channel_type = details.channel_type.clone().unwrap();
let feerate =
if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() { 0 } else { 253 };
let anchors_sat =
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
ANCHOR_OUTPUT_VALUE_SATOSHI * 2
} else {
0
};
let initiator_value_to_self_sat = if counterparty_has_output {
send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000);
channel_value_sat / 2
} else if !node_0_is_initiator {
let tx_fee_msat = chan_utils::commit_tx_fee_sat(feerate, 2, &channel_type) * 1000;
let node_0_details = &nodes[0].node.list_channels()[0];
let outbound_capacity_msat = node_0_details.outbound_capacity_msat;
let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat;
assert_eq!(outbound_capacity_msat, (channel_value_sat - anchors_sat) * 1000);
assert_eq!(available_capacity_msat, outbound_capacity_msat - tx_fee_msat);
send_payment(&nodes[0], &[&nodes[1]], available_capacity_msat);
let node_0_to_local_output_msat = channel_value_sat * 1000
- available_capacity_msat
- anchors_sat * 1000
- chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type) * 1000;
assert!(node_0_to_local_output_msat / 1000 < dust_limit_satoshis);
let commit_tx = &get_local_commitment_txn!(nodes[0], channel_id)[0];
assert_eq!(
commit_tx.output.len(),
if channel_type == ChannelTypeFeatures::only_static_remote_key() { 1 } else { 2 }
);
assert_eq!(
commit_tx.output.last().unwrap().value,
Amount::from_sat(available_capacity_msat / 1000)
);
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
assert_eq!(commit_tx.output[0].value, Amount::from_sat(330));
} else if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() {
assert_eq!(commit_tx.output[0].value, Amount::ZERO);
}
available_capacity_msat / 1000
} else {
channel_value_sat
};
let estimated_fees_sat = 183;
let mut splice_out_max_value = if counterparty_has_output && node_0_is_initiator {
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 1, &channel_type);
Amount::from_sat(
initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - estimated_fees_sat,
)
} else if !counterparty_has_output && node_0_is_initiator {
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type);
Amount::from_sat(
initiator_value_to_self_sat
- commit_tx_fee_sat
- anchors_sat - estimated_fees_sat
- dust_limit_satoshis,
)
} else if counterparty_has_output && !node_0_is_initiator {
Amount::from_sat(initiator_value_to_self_sat - estimated_fees_sat)
} else if !counterparty_has_output && !node_0_is_initiator {
Amount::from_sat(initiator_value_to_self_sat - estimated_fees_sat - dust_limit_satoshis)
} else {
panic!("unexpected case!");
};
if channel_value_sat
< splice_out_max_value.to_sat() + estimated_fees_sat + MIN_CHANNEL_VALUE_SATOSHIS
{
splice_out_max_value = Amount::from_sat(
channel_value_sat.saturating_sub(estimated_fees_sat + MIN_CHANNEL_VALUE_SATOSHIS),
);
}
let outputs = vec![TxOut {
value: splice_out_max_value + if splice_passes { Amount::ZERO } else { Amount::ONE_SAT },
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let (initiator, acceptor) =
if node_0_is_initiator { (&nodes[0], &nodes[1]) } else { (&nodes[1], &nodes[0]) };
let initiator_details = &initiator.node.list_channels()[0];
assert_eq!(
initiator_details.next_splice_out_maximum_sat,
splice_out_max_value.to_sat() + estimated_fees_sat
);
if splice_passes {
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let (splice_tx, _) = splice_channel(initiator, acceptor, channel_id, contribution);
mine_transaction(initiator, &splice_tx);
mine_transaction(acceptor, &splice_tx);
lock_splice_after_blocks(initiator, acceptor, ANTI_REORG_DELAY - 1);
} else {
assert!(matches!(
build_splice_out_contribution(initiator, acceptor, channel_id, outputs),
Err(FundingContributionError::InvalidSpliceValue),
));
}
channel_type
}
#[cfg(test)]
fn do_test_0reserve_splice_counterparty_validation(
splice_passes: bool, counterparty_has_output: bool, node_0_is_initiator: bool,
mut config: UserConfig,
) -> ChannelTypeFeatures {
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs =
create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config.clone())]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let _node_id_0 = nodes[0].node.get_our_node_id();
let _node_id_1 = nodes[1].node.get_our_node_id();
let channel_value_sat = 100_000;
let dust_limit_satoshis = 546;
let (channel_id, _tx) =
setup_0reserve_no_outputs_channels(&nodes, channel_value_sat, dust_limit_satoshis);
let details = &nodes[0].node.list_channels()[0];
let channel_type = details.channel_type.clone().unwrap();
let feerate =
if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() { 0 } else { 253 };
let anchors_sat =
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
ANCHOR_OUTPUT_VALUE_SATOSHI * 2
} else {
0
};
let initiator_value_to_self_sat = if counterparty_has_output {
send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000);
channel_value_sat / 2
} else if !node_0_is_initiator {
let tx_fee_msat = chan_utils::commit_tx_fee_sat(feerate, 2, &channel_type) * 1000;
let node_0_details = &nodes[0].node.list_channels()[0];
let outbound_capacity_msat = node_0_details.outbound_capacity_msat;
let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat;
assert_eq!(outbound_capacity_msat, (channel_value_sat - anchors_sat) * 1000);
assert_eq!(available_capacity_msat, outbound_capacity_msat - tx_fee_msat);
send_payment(&nodes[0], &[&nodes[1]], available_capacity_msat);
let node_0_to_local_output_msat = channel_value_sat * 1000
- available_capacity_msat
- anchors_sat * 1000
- chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type) * 1000;
assert!(node_0_to_local_output_msat / 1000 < dust_limit_satoshis);
let commit_tx = &get_local_commitment_txn!(nodes[0], channel_id)[0];
assert_eq!(
commit_tx.output.len(),
if channel_type == ChannelTypeFeatures::only_static_remote_key() { 1 } else { 2 }
);
assert_eq!(
commit_tx.output.last().unwrap().value,
Amount::from_sat(available_capacity_msat / 1000)
);
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
assert_eq!(commit_tx.output[0].value, Amount::from_sat(330));
} else if channel_type == ChannelTypeFeatures::anchors_zero_fee_commitments() {
assert_eq!(commit_tx.output[0].value, Amount::ZERO);
}
available_capacity_msat / 1000
} else {
channel_value_sat
};
let mut splice_out_value_incl_fees = if counterparty_has_output && node_0_is_initiator {
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 1, &channel_type);
Amount::from_sat(initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat)
} else if !counterparty_has_output && node_0_is_initiator {
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type);
Amount::from_sat(
initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - dust_limit_satoshis,
)
} else if counterparty_has_output && !node_0_is_initiator {
Amount::from_sat(initiator_value_to_self_sat)
} else if !counterparty_has_output && !node_0_is_initiator {
Amount::from_sat(initiator_value_to_self_sat - dust_limit_satoshis)
} else {
panic!("unexpected case!");
};
if channel_value_sat < splice_out_value_incl_fees.to_sat() + MIN_CHANNEL_VALUE_SATOSHIS {
splice_out_value_incl_fees =
Amount::from_sat(channel_value_sat.saturating_sub(MIN_CHANNEL_VALUE_SATOSHIS));
}
let (initiator, acceptor) =
if node_0_is_initiator { (&nodes[0], &nodes[1]) } else { (&nodes[1], &nodes[0]) };
let initiator_details = &initiator.node.list_channels()[0];
assert_eq!(initiator_details.next_splice_out_maximum_sat, splice_out_value_incl_fees.to_sat());
let funding_contribution_sat =
-(splice_out_value_incl_fees.to_sat() as i64) - if splice_passes { 0 } else { 1 };
let post_channel_value_sat =
channel_value_sat.checked_add_signed(funding_contribution_sat).unwrap();
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let _contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.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 mut splice_init =
get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
splice_init.funding_contribution_satoshis = funding_contribution_sat;
if splice_passes {
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
let _splice_ack =
get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
} else {
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
let msg = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
assert_eq!(msg.channel_id, channel_id);
let cannot_splice_out = if u64::try_from(funding_contribution_sat.abs()).unwrap()
> initiator_value_to_self_sat
{
format!(
"Their contribution candidate {funding_contribution_sat}sat \
is greater than their total balance in the channel {initiator_value_to_self_sat}sat"
)
} else if post_channel_value_sat < MIN_CHANNEL_VALUE_SATOSHIS {
format!(
"Spliced channel value must be at least {MIN_CHANNEL_VALUE_SATOSHIS} satoshis. \
It would be {post_channel_value_sat}"
)
} else {
"Balance exhausted on local commitment".to_string()
};
assert_eq!(tx_abort_data(&msg), format!("Invalid contribution: {cannot_splice_out}"));
acceptor.logger.assert_log(
"lightning::ln::channelmanager",
format!("Got non-closing error: Invalid contribution: {cannot_splice_out}"),
1,
);
}
channel_type
}
#[cfg(test)]
enum AcceptorBalance {
NoBalance,
BalanceInHTLC,
SettledBalance,
}
#[cfg(test)]
enum ValidationCase {
Passes,
FailsAtHolder,
FailsAtCounterparty,
}
#[test]
fn test_splice_out_initiator_reserve_breach_zero_fee_commitments() {
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::NoBalance,
ValidationCase::Passes,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::BalanceInHTLC,
ValidationCase::Passes,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::SettledBalance,
ValidationCase::Passes,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::NoBalance,
ValidationCase::FailsAtHolder,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::BalanceInHTLC,
ValidationCase::FailsAtHolder,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::SettledBalance,
ValidationCase::FailsAtHolder,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::NoBalance,
ValidationCase::FailsAtCounterparty,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::BalanceInHTLC,
ValidationCase::FailsAtCounterparty,
);
do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
AcceptorBalance::SettledBalance,
ValidationCase::FailsAtCounterparty,
);
}
#[cfg(test)]
fn do_test_splice_out_initiator_reserve_breach_zero_fee_commitments(
acceptor_balance: AcceptorBalance, validation_case: ValidationCase,
) {
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.negotiate_anchor_zero_fee_commitments = true;
config.channel_handshake_config.our_htlc_minimum_msat = 1;
let node_chanmgrs =
create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config.clone())]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let _node_id_0 = nodes[0].node.get_our_node_id();
let _node_id_1 = nodes[1].node.get_our_node_id();
let channel_value_sat = 100_000;
let node_1_settled_balance_msat =
if matches!(acceptor_balance, AcceptorBalance::SettledBalance) { 1 } else { 0 };
let node_1_htlc_balance_msat =
if matches!(acceptor_balance, AcceptorBalance::BalanceInHTLC) { 1 } else { 0 };
let node_0_balance_msat =
channel_value_sat * 1000 - node_1_settled_balance_msat - node_1_htlc_balance_msat;
let high_dust_limit_satoshis = 10_000;
let (_, _, channel_id, _tx) = create_announced_chan_between_nodes_with_value(
&nodes,
0,
1,
channel_value_sat,
node_1_settled_balance_msat,
);
if matches!(acceptor_balance, AcceptorBalance::BalanceInHTLC) {
let _ = route_payment(&nodes[0], &[&nodes[1]], node_1_htlc_balance_msat);
}
let initiator = &nodes[0];
let acceptor = &nodes[1];
let node_id_initiator = initiator.node.get_our_node_id();
let node_id_acceptor = acceptor.node.get_our_node_id();
let stale_funding_template =
nodes[0].node.splice_channel(&channel_id, &node_id_acceptor).unwrap();
let splice_out = |funding_template: FundingTemplate, outputs: Vec<TxOut>| {
let floor_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let feerate = funding_template.min_rbf_feerate().unwrap_or(floor_feerate);
let funding_contribution =
funding_template.splice_out(outputs, feerate, FeeRate::MAX).unwrap();
match nodes[0].node.funding_contributed(
&channel_id,
&node_id_acceptor,
funding_contribution.clone(),
None,
) {
Ok(()) => Ok(funding_contribution),
Err(e) => {
expect_splice_failed_events(
&nodes[0],
&channel_id,
funding_contribution,
NegotiationFailureReason::ContributionInvalid,
);
Err(e)
},
}
};
{
let per_peer_lock;
let mut peer_state_lock;
let channel =
get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, channel_id);
if let Some(chan) = channel.as_funded_mut() {
chan.context.holder_dust_limit_satoshis = high_dust_limit_satoshis;
} else {
panic!("Unexpected Channel phase");
}
}
{
let per_peer_lock;
let mut peer_state_lock;
let channel =
get_channel_ref!(nodes[1], nodes[0], per_peer_lock, peer_state_lock, channel_id);
if let Some(chan) = channel.as_funded_mut() {
chan.context.counterparty_dust_limit_satoshis = high_dust_limit_satoshis;
} else {
panic!("Unexpected Channel phase");
}
}
if matches!(validation_case, ValidationCase::Passes) {
let node_0_balance_leftover_amount = Amount::from_sat(high_dust_limit_satoshis);
let estimated_fees = 183;
let splice_out_output_sat =
node_0_balance_msat / 1000 - node_0_balance_leftover_amount.to_sat() - estimated_fees;
let splice_out_output_amount = Amount::from_sat(splice_out_output_sat);
let outputs = vec![TxOut {
value: splice_out_output_amount,
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let contribution = splice_out(stale_funding_template, outputs).unwrap();
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
mine_transaction(&nodes[0], &splice_tx);
mine_transaction(&nodes[1], &splice_tx);
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
} else {
let node_0_balance_leftover_amount = Amount::from_sat(high_dust_limit_satoshis - 1);
let funding_contribution_sat =
-((node_0_balance_msat / 1000 - node_0_balance_leftover_amount.to_sat()) as i64);
let value = if matches!(validation_case, ValidationCase::FailsAtHolder) {
Amount::from_sat(funding_contribution_sat.unsigned_abs() - 183)
} else if matches!(validation_case, ValidationCase::FailsAtCounterparty) {
Amount::from_sat(1000)
} else {
panic!("Unexpected test case");
};
let outputs = vec![TxOut {
value,
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}];
let contribution = splice_out(stale_funding_template, outputs);
if matches!(validation_case, ValidationCase::FailsAtHolder) {
assert_eq!(
contribution.unwrap_err(),
APIError::APIMisuseError {
err: format!("Channel {channel_id} cannot accept funding contribution"),
}
);
let splice_out_value = value + Amount::from_sat(183);
let splice_out_max = splice_out_value - Amount::ONE_SAT;
let cannot_splice_out = format!(
"Channel {channel_id} cannot be funded: \
Our splice-out value of {splice_out_value} is greater than the \
maximum {splice_out_max}"
);
nodes[0].logger.assert_log("lightning::ln::channel", cannot_splice_out, 1);
return;
}
assert!(contribution.is_ok());
let v2_channel_reserve = Amount::from_sat(high_dust_limit_satoshis);
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 mut splice_init =
get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
splice_init.funding_contribution_satoshis = funding_contribution_sat;
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
let msg = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
assert_eq!(msg.channel_id, channel_id);
let post_splice_channel_value_sat = node_0_balance_leftover_amount.to_sat();
let cannot_splice_out = if matches!(acceptor_balance, AcceptorBalance::NoBalance) {
format!(
"The post-splice channel value {post_splice_channel_value_sat} \
is smaller than their dust limit {high_dust_limit_satoshis}"
)
} else {
assert_eq!(
channel_value_sat.checked_add_signed(funding_contribution_sat).unwrap(),
high_dust_limit_satoshis
);
format!(
"Their post-splice channel balance \
{node_0_balance_leftover_amount} is smaller than our selected v2 reserve \
{v2_channel_reserve}"
)
};
assert_eq!(tx_abort_data(&msg), format!("Invalid contribution: {cannot_splice_out}"));
acceptor.logger.assert_log(
"lightning::ln::channelmanager",
format!("Got non-closing error: Invalid contribution: {cannot_splice_out}"),
1,
);
}
}
#[test]
fn test_splice_out_maximum_on_both_commitments_dust_on_fundee_commitment() {
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
const CHANNEL_VALUE_SAT: u64 = 100_000;
const FEERATE: u32 = 253;
const TOTAL_ANCHORS_SAT: u64 = 2 * 330;
const NODE_0_DUST_LIMIT_SAT: u64 = 354;
const NODE_1_DUST_LIMIT_SAT: u64 = 10_000;
let (channel_id, _transaction) =
setup_0reserve_no_outputs_channels(&nodes, CHANNEL_VALUE_SAT, NODE_0_DUST_LIMIT_SAT);
{
let per_peer_state_lock;
let mut peer_state_lock;
let chan =
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id);
chan.context_mut().counterparty_dust_limit_satoshis = NODE_1_DUST_LIMIT_SAT;
assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_0_DUST_LIMIT_SAT);
assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0);
assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0));
}
{
let per_peer_state_lock;
let mut peer_state_lock;
let chan =
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id);
chan.context_mut().holder_dust_limit_satoshis = NODE_1_DUST_LIMIT_SAT;
assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_0_DUST_LIMIT_SAT);
assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0);
assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0));
}
let details = &nodes[0].node.list_channels()[0];
let channel_type = details.channel_type.clone().unwrap();
assert_eq!(channel_type, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
const SNEAKY_HTLC_SAT: u64 = 5_000;
let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], SNEAKY_HTLC_SAT * 1000);
let node_0_details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 0, &channel_type);
let expected_next_splice_out_maximum_sat = CHANNEL_VALUE_SAT
- SNEAKY_HTLC_SAT
- TOTAL_ANCHORS_SAT
- reserved_fee_sat
- NODE_1_DUST_LIMIT_SAT;
assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
let node_1_details = &nodes[1].node.list_channels()[0];
assert_eq!(node_1_details.next_splice_out_maximum_sat, 0);
fail_payment(&nodes[0], &[&nodes[1]], payment_hash);
let details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 2, &channel_type);
let expected_available_capacity_sat = CHANNEL_VALUE_SAT - TOTAL_ANCHORS_SAT - reserved_fee_sat;
assert_eq!(details.next_outbound_htlc_limit_msat, expected_available_capacity_sat * 1000);
let node_0_payment_sat = expected_available_capacity_sat;
send_payment(&nodes[0], &[&nodes[1]], node_0_payment_sat * 1000);
assert!(TOTAL_ANCHORS_SAT + reserved_fee_sat < NODE_1_DUST_LIMIT_SAT);
let details = &nodes[1].node.list_channels()[0];
let expected_next_splice_out_maximum_sat = node_0_payment_sat - NODE_1_DUST_LIMIT_SAT;
assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
let details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 1, &channel_type);
let expected_next_splice_out_maximum_sat =
CHANNEL_VALUE_SAT - node_0_payment_sat - TOTAL_ANCHORS_SAT - reserved_fee_sat;
assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
}
#[test]
fn test_splice_out_maximum_on_both_commitments_dust_on_funder_commitment() {
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
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.announced_channel_max_inbound_htlc_value_in_flight_percentage =
100;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
const CHANNEL_VALUE_SAT: u64 = 100_000;
const FEERATE: u32 = 253;
const TOTAL_ANCHORS_SAT: u64 = 2 * 330;
const NODE_0_DUST_LIMIT_SAT: u64 = 10_000;
const NODE_1_DUST_LIMIT_SAT: u64 = 354;
let (channel_id, _transaction) =
setup_0reserve_no_outputs_channels(&nodes, CHANNEL_VALUE_SAT, NODE_1_DUST_LIMIT_SAT);
{
let per_peer_state_lock;
let mut peer_state_lock;
let chan =
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id);
chan.context_mut().holder_dust_limit_satoshis = NODE_0_DUST_LIMIT_SAT;
assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_1_DUST_LIMIT_SAT);
assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0);
assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0));
}
{
let per_peer_state_lock;
let mut peer_state_lock;
let chan =
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id);
chan.context_mut().counterparty_dust_limit_satoshis = NODE_0_DUST_LIMIT_SAT;
assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_1_DUST_LIMIT_SAT);
assert_eq!(chan.funding().holder_selected_channel_reserve_satoshis, 0);
assert_eq!(chan.funding().counterparty_selected_channel_reserve_satoshis, Some(0));
}
let details = &nodes[0].node.list_channels()[0];
let channel_type = details.channel_type.clone().unwrap();
assert_eq!(channel_type, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
const SNEAKY_HTLC_SAT: u64 = 5_000;
let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], SNEAKY_HTLC_SAT * 1000);
let node_0_details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 0, &channel_type);
let expected_next_splice_out_maximum_sat = CHANNEL_VALUE_SAT
- SNEAKY_HTLC_SAT
- TOTAL_ANCHORS_SAT
- reserved_fee_sat
- NODE_0_DUST_LIMIT_SAT;
assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
let node_1_details = &nodes[1].node.list_channels()[0];
assert_eq!(node_1_details.next_splice_out_maximum_sat, 0);
fail_payment(&nodes[0], &[&nodes[1]], payment_hash);
let details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 2, &channel_type);
let expected_available_capacity_sat = CHANNEL_VALUE_SAT - TOTAL_ANCHORS_SAT - reserved_fee_sat;
assert_eq!(details.next_outbound_htlc_limit_msat, expected_available_capacity_sat * 1000);
let node_0_payment_sat = expected_available_capacity_sat;
send_payment(&nodes[0], &[&nodes[1]], node_0_payment_sat * 1000);
assert!(TOTAL_ANCHORS_SAT + reserved_fee_sat < NODE_0_DUST_LIMIT_SAT);
let details = &nodes[1].node.list_channels()[0];
let expected_next_splice_out_maximum_sat = node_0_payment_sat - NODE_0_DUST_LIMIT_SAT;
assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
let details = &nodes[0].node.list_channels()[0];
let reserved_fee_sat = chan_utils::commit_tx_fee_sat(FEERATE, 1, &channel_type);
let expected_next_splice_out_maximum_sat =
CHANNEL_VALUE_SAT - node_0_payment_sat - TOTAL_ANCHORS_SAT - reserved_fee_sat;
assert_eq!(details.next_splice_out_maximum_sat, expected_next_splice_out_maximum_sat);
}
#[test]
fn test_splice_out_maximum_includes_pending_claimed_inbound_htlc() {
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);
const CHANNEL_VALUE_MSAT: u64 = 100_000_000;
const PENDING_CLAIMED_INBOUND_HTLC_MSAT: u64 = 10_000_000;
let node_id_0 = nodes[0].node.get_our_node_id();
let (_, _, channel_id, _) =
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, CHANNEL_VALUE_MSAT / 1000, 0);
let (payment_preimage, payment_hash, ..) =
route_payment(&nodes[0], &[&nodes[1]], PENDING_CLAIMED_INBOUND_HTLC_MSAT);
nodes[1].node.claim_funds(payment_preimage);
check_added_monitors(&nodes[1], 1);
expect_payment_claimed!(nodes[1], payment_hash, PENDING_CLAIMED_INBOUND_HTLC_MSAT);
let updates = get_htlc_update_msgs(&nodes[1], &node_id_0);
assert!(updates.update_add_htlcs.is_empty());
assert_eq!(updates.update_fulfill_htlcs.len(), 1);
assert!(updates.update_fail_htlcs.is_empty());
assert!(updates.update_fail_malformed_htlcs.is_empty());
assert!(updates.update_fee.is_none());
assert_eq!(updates.commitment_signed.len(), 1);
let node_1_details = &nodes[1].node.list_channels()[0];
let local_balance_before_fee_sat = PENDING_CLAIMED_INBOUND_HTLC_MSAT / 1000;
let dividend_sat = local_balance_before_fee_sat * 100 + 100 - CHANNEL_VALUE_MSAT / 1000;
let expected_splice_out_max = (dividend_sat - 1) / 99;
assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max);
assert!(nodes[1].node.splice_channel(&channel_id, &node_id_0).is_ok());
}
#[test]
fn test_async_splice_receives_tx_signatures_while_unrelated_monitor_update_pending() {
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
create_announced_chan_between_nodes(&nodes, 1, 2);
let (initiator, acceptor) = (&nodes[0], &nodes[1]);
let initiator_node_id = initiator.node.get_our_node_id();
let acceptor_node_id = acceptor.node.get_our_node_id();
let final_node_id = nodes[2].node.get_our_node_id();
let (payment_preimage, payment_hash, ..) =
route_payment(initiator, &[acceptor, &nodes[2]], 1_000_000);
acceptor.disable_channel_signer_op(
&initiator_node_id,
&channel_id,
SignerOp::SignCounterpartyCommitment,
);
let outputs = vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: initiator.wallet_source.get_change_script().unwrap(),
}];
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
negotiate_splice_tx(initiator, acceptor, channel_id, contribution);
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
let partially_signed_tx = initiator.wallet_source.sign_tx(unsigned_transaction).unwrap();
initiator
.node
.funding_transaction_signed(&channel_id, &acceptor_node_id, partially_signed_tx)
.unwrap();
}
let initiator_commit_sig = get_htlc_update_msgs(initiator, &acceptor_node_id);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
acceptor
.node
.handle_commitment_signed(initiator_node_id, &initiator_commit_sig.commitment_signed[0]);
check_added_monitors(acceptor, 1);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
acceptor.enable_channel_signer_op(
&initiator_node_id,
&channel_id,
SignerOp::SignCounterpartyCommitment,
);
acceptor.node.signer_unblocked(None);
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
initiator.node.handle_commitment_signed(acceptor_node_id, &updates.commitment_signed[0]);
check_added_monitors(initiator, 1);
} else {
panic!("Unexpected event");
}
acceptor.chain_monitor.complete_sole_pending_chan_update(&channel_id);
let acceptor_tx_signatures =
get_event_msg!(acceptor, MessageSendEvent::SendTxSignatures, initiator_node_id);
initiator.node.handle_tx_signatures(acceptor_node_id, &acceptor_tx_signatures);
let delayed_initiator_tx_signatures =
get_event_msg!(initiator, MessageSendEvent::SendTxSignatures, acceptor_node_id);
let mut broadcasted = initiator.tx_broadcaster.txn_broadcast();
assert_eq!(broadcasted.len(), 1, "{broadcasted:?}");
let splice_tx = broadcasted.pop().unwrap();
mine_transaction(initiator, &splice_tx);
mine_transaction(acceptor, &splice_tx);
let _ = get_event!(initiator, Event::SpliceNegotiated);
nodes[2].node.claim_funds(payment_preimage);
check_added_monitors(&nodes[2], 1);
expect_payment_claimed!(nodes[2], payment_hash, 1_000_000);
let mut commitment_update = get_htlc_update_msgs(&nodes[2], &acceptor_node_id);
assert_eq!(commitment_update.update_fulfill_htlcs.len(), 1);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
acceptor.node.handle_update_fulfill_htlc(
final_node_id,
commitment_update.update_fulfill_htlcs.remove(0),
);
check_added_monitors(acceptor, 1);
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
acceptor.node.handle_tx_signatures(initiator_node_id, &delayed_initiator_tx_signatures);
acceptor.chain_monitor.complete_sole_pending_chan_update(&channel_id);
let mut update_fulfill = get_htlc_update_msgs(acceptor, &initiator_node_id);
check_added_monitors(acceptor, 1);
let payment_forwarded = get_event!(acceptor, Event::PaymentForwarded);
expect_payment_forwarded(
payment_forwarded,
acceptor,
initiator,
&nodes[2],
Some(1000),
None,
false,
false,
false,
);
do_commitment_signed_dance(
acceptor,
&nodes[2],
&commitment_update.commitment_signed,
false,
false,
);
initiator.node.handle_update_fulfill_htlc(
acceptor_node_id,
update_fulfill.update_fulfill_htlcs.remove(0),
);
do_commitment_signed_dance(
initiator,
acceptor,
&update_fulfill.commitment_signed,
false,
false,
);
expect_payment_sent(initiator, payment_preimage, None, true, true);
}
#[cfg(test)]
fn candidate_txid(candidate: &SpliceCandidateDetails) -> Txid {
match candidate.status {
SpliceCandidateStatus::AwaitingSignatures { txid, .. }
| SpliceCandidateStatus::Negotiated { txid, .. } => txid,
ref other => panic!("candidate status carries no txid: {other:?}"),
}
}
#[cfg(test)]
fn candidate_value(candidate: &SpliceCandidateDetails) -> u64 {
match candidate.status {
SpliceCandidateStatus::ConstructingTransaction { new_channel_value_satoshis, .. }
| SpliceCandidateStatus::AwaitingSignatures { new_channel_value_satoshis, .. }
| SpliceCandidateStatus::Negotiated { new_channel_value_satoshis, .. } => {
new_channel_value_satoshis
},
ref other => panic!("candidate status carries no value: {other:?}"),
}
}
#[test]
fn test_channel_details_pending_splice() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let (persister_0, persister_1);
let (chain_monitor_0, chain_monitor_1);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let (node_0, node_1);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 splice_details = |node: &Node<'_, '_, '_>| {
node.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
};
assert_eq!(splice_details(&nodes[0]), None);
assert_eq!(splice_details(&nodes[1]), None);
let added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
assert_eq!(
splice_details(&nodes[0]),
Some(SpliceDetails {
candidates: vec![SpliceCandidateDetails {
contribution: Some(contribution.clone()),
status: SpliceCandidateStatus::WaitingOnQuiescence,
}],
confirmed_candidate: None,
received_splice_locked_txid: None,
}),
);
assert_eq!(splice_details(&nodes[1]), None);
let new_channel_value_sat =
(initial_channel_value_sat as i64 + contribution.net_value().to_sat()) as u64;
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 1);
assert_eq!(
details.candidates[0].status,
SpliceCandidateStatus::AwaitingAck {
is_initiator: true,
funding_feerate_sat_per_1000_weight: FEERATE_FLOOR_SATS_PER_KW,
},
);
assert_eq!(details.candidates[0].contribution, Some(contribution.clone()));
assert_eq!(splice_details(&nodes[1]), None);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let details = splice_details(&nodes[1]).unwrap();
assert_eq!(details.candidates.len(), 1);
assert_eq!(
details.candidates[0].status,
SpliceCandidateStatus::ConstructingTransaction {
is_initiator: false,
funding_feerate_sat_per_1000_weight: FEERATE_FLOOR_SATS_PER_KW,
new_channel_value_satoshis: new_channel_value_sat,
},
);
assert_eq!(details.candidates[0].contribution, None);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 1);
assert_eq!(
details.candidates[0].status,
SpliceCandidateStatus::ConstructingTransaction {
is_initiator: true,
funding_feerate_sat_per_1000_weight: FEERATE_FLOOR_SATS_PER_KW,
new_channel_value_satoshis: new_channel_value_sat,
},
);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution.clone(),
new_funding_script.clone(),
);
let details_0 = splice_details(&nodes[0]).unwrap();
let details_1 = splice_details(&nodes[1]).unwrap();
assert_eq!(details_0.candidates.len(), 1);
assert_eq!(details_1.candidates.len(), 1);
assert!(matches!(
details_0.candidates[0].status,
SpliceCandidateStatus::AwaitingSignatures { is_initiator: true, .. }
));
assert!(matches!(
details_1.candidates[0].status,
SpliceCandidateStatus::AwaitingSignatures { is_initiator: false, .. }
));
assert_eq!(candidate_txid(&details_0.candidates[0]), candidate_txid(&details_1.candidates[0]));
assert_eq!(candidate_value(&details_0.candidates[0]), new_channel_value_sat);
assert_eq!(details_0.candidates[0].contribution, Some(contribution.clone()));
assert_eq!(details_1.candidates[0].contribution, None);
let (splice_tx, splice_locked) =
sign_interactive_funding_tx(SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]));
assert!(splice_locked.is_none());
assert_eq!(candidate_txid(&details_0.candidates[0]), splice_tx.compute_txid());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 1);
assert!(matches!(details.candidates[0].status, SpliceCandidateStatus::Negotiated { .. }));
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(candidate_value(&details.candidates[0]), new_channel_value_sat);
assert_eq!(details.candidates[0].contribution, Some(contribution.clone()));
assert_eq!(details.confirmed_candidate, None);
assert_eq!(details.received_splice_locked_txid, None);
let details = splice_details(&nodes[1]).unwrap();
assert_eq!(details.candidates.len(), 1);
assert!(matches!(details.candidates[0].status, SpliceCandidateStatus::Negotiated { .. }));
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[0].contribution, None);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate_sat_per_kwu = FEERATE_FLOOR_SATS_PER_KW as u64 + 25;
let rbf_feerate = FeeRate::from_sat_per_kwu(rbf_feerate_sat_per_kwu);
let rbf_contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[1].status, SpliceCandidateStatus::WaitingOnQuiescence);
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution.clone()));
let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_init);
let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_ack);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(
details.candidates[1].status,
SpliceCandidateStatus::AwaitingAck {
is_initiator: true,
funding_feerate_sat_per_1000_weight: rbf_feerate_sat_per_kwu as u32,
},
);
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution.clone()));
let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1);
nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf);
let tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0);
nodes[0].node.handle_tx_ack_rbf(node_id_1, &tx_ack_rbf);
let rbf_channel_value_sat =
(initial_channel_value_sat as i64 + rbf_contribution.net_value().to_sat()) as u64;
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[0].contribution, Some(contribution.clone()));
assert_eq!(
details.candidates[1].status,
SpliceCandidateStatus::ConstructingTransaction {
is_initiator: true,
funding_feerate_sat_per_1000_weight: rbf_feerate_sat_per_kwu as u32,
new_channel_value_satoshis: rbf_channel_value_sat,
},
);
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution.clone()));
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
rbf_contribution.clone(),
new_funding_script,
);
let (rbf_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).replacing(splice_tx.compute_txid()),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert!(details
.candidates
.iter()
.all(|c| matches!(c.status, SpliceCandidateStatus::Negotiated { .. })));
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[0].contribution, Some(contribution.clone()));
assert_eq!(candidate_txid(&details.candidates[1]), rbf_tx.compute_txid());
assert_eq!(candidate_value(&details.candidates[1]), rbf_channel_value_sat);
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution.clone()));
let details = splice_details(&nodes[1]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(details.candidates[1].contribution, None);
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
reload_node!(
nodes[0],
&nodes[0].node.encode(),
&[&encoded_monitor_0],
persister_0,
chain_monitor_0,
node_0
);
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
reload_node!(
nodes[1],
&nodes[1].node.encode(),
&[&encoded_monitor_1],
persister_1,
chain_monitor_1,
node_1
);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[0].contribution, Some(contribution));
assert_eq!(candidate_txid(&details.candidates[1]), rbf_tx.compute_txid());
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution));
let details = splice_details(&nodes[1]).unwrap();
assert_eq!(details.candidates.len(), 2);
assert!(details.candidates.iter().all(|candidate| candidate.contribution.is_none()));
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_args);
mine_transaction(&nodes[0], &rbf_tx);
mine_transaction(&nodes[1], &rbf_tx);
let details = splice_details(&nodes[0]).unwrap();
let confirmed = details.confirmed_candidate.unwrap();
assert_eq!(confirmed.txid, rbf_tx.compute_txid());
assert_eq!(confirmed.confirmations, 1);
assert_eq!(confirmed.confirmations_required, 6);
assert!(!confirmed.splice_locked_sent);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.received_splice_locked_txid, None);
let confirmed = details.confirmed_candidate.unwrap();
assert_eq!(confirmed.txid, rbf_tx.compute_txid());
assert!(confirmed.splice_locked_sent);
assert_eq!(confirmed.confirmations, ANTI_REORG_DELAY);
lock_splice(&nodes[0], &nodes[1], &splice_locked, false, &[splice_tx.compute_txid()]);
assert_eq!(splice_details(&nodes[0]), None);
assert_eq!(splice_details(&nodes[1]), None);
}
#[test]
fn test_channel_details_first_contribution_on_rbf() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value);
let (splice_tx, _) = splice_channel(&nodes[1], &nodes[0], channel_id, contribution);
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
let wallet = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let rbf_contribution = funding_template
.without_prior_contribution(rbf_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet)
.add_value(added_value)
.unwrap()
.build()
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, rbf_contribution.clone(), None)
.unwrap();
complete_rbf_handshake(&nodes[0], &nodes[1]);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddInput, node_id_1);
let channels = nodes[0].node.list_channels();
let details = channels[0].splice_details.as_ref().unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert_eq!(details.candidates[0].contribution, None);
assert!(matches!(
details.candidates[1].status,
SpliceCandidateStatus::ConstructingTransaction { is_initiator: true, .. }
));
assert_eq!(details.candidates[1].contribution, Some(rbf_contribution.clone()));
let channels = nodes[1].node.list_channels();
let details = channels[0].splice_details.as_ref().unwrap();
assert!(matches!(
details.candidates[1].status,
SpliceCandidateStatus::ConstructingTransaction { is_initiator: false, .. }
));
assert!(details.candidates[1].contribution.is_some());
assert!(details.candidates[0].contribution.is_some());
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
expect_splice_failed_events(
&nodes[0],
&channel_id,
rbf_contribution,
NegotiationFailureReason::PeerDisconnected,
);
let _ = get_event!(&nodes[1], Event::SpliceNegotiationFailed);
let channels = nodes[0].node.list_channels();
let details = channels[0].splice_details.as_ref().unwrap();
assert_eq!(details.candidates.len(), 1);
assert_eq!(details.candidates[0].contribution, None);
let channels = nodes[1].node.list_channels();
let details = channels[0].splice_details.as_ref().unwrap();
assert_eq!(details.candidates.len(), 1);
assert!(details.candidates[0].contribution.is_some());
}
#[test]
fn test_channel_details_zero_conf_splice() {
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_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let (funding_tx, channel_id) =
open_zero_conf_channel_with_value(&nodes[0], &nodes[1], None, initial_channel_value_sat, 0);
let added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script,
);
let (splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.zero_conf()
.with_unconfirmed_funding(funding_tx.compute_txid()),
);
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let details = nodes[0]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
let confirmed =
details.confirmed_candidate.expect("the locked zero-conf candidate should be reported");
assert_eq!(confirmed.txid, splice_tx.compute_txid());
assert_eq!(confirmed.confirmations, 0);
assert_eq!(confirmed.confirmations_required, 0);
assert!(confirmed.splice_locked_sent);
assert_eq!(details.received_splice_locked_txid, None);
let (splice_locked_for_node_1, _) =
splice_locked.expect("a zero-conf splice sends splice_locked at signing");
lock_splice(&nodes[0], &nodes[1], &splice_locked_for_node_1, true, &[]);
let splice_details = |node: &Node<'_, '_, '_>| {
node.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
};
assert_eq!(splice_details(&nodes[0]), None);
assert_eq!(splice_details(&nodes[1]), None);
}
#[test]
fn test_channel_details_waiting_on_lock_zero_conf() {
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_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let node_id_1 = nodes[1].node.get_our_node_id();
let initial_channel_value_sat = 100_000;
let (funding_tx, channel_id) =
open_zero_conf_channel_with_value(&nodes[0], &nodes[1], None, initial_channel_value_sat, 0);
let added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
contribution,
new_funding_script,
);
let _ = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.zero_conf()
.with_unconfirmed_funding(funding_tx.compute_txid()),
);
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
nodes[0].node.get_and_clear_pending_msg_events();
let queued = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let details = nodes[0]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(details.candidates[1].status, SpliceCandidateStatus::WaitingOnLock);
assert_eq!(details.candidates[1].contribution, Some(queued));
nodes[0].node.get_and_clear_pending_msg_events();
nodes[1].node.get_and_clear_pending_msg_events();
}
#[test]
fn test_channel_details_received_splice_locked() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
mine_transaction(&nodes[0], &splice_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked);
let details = nodes[1]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.received_splice_locked_txid, Some(splice_tx.compute_txid()));
assert_eq!(details.confirmed_candidate, None);
assert_eq!(details.candidates.len(), 1);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
nodes[1].node.get_and_clear_pending_msg_events();
let queued = do_initiate_splice_in(&nodes[1], &nodes[0], channel_id, Amount::from_sat(25_000));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let details = nodes[1]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 2);
assert_eq!(details.candidates[1].status, SpliceCandidateStatus::WaitingOnLock);
assert_eq!(details.candidates[1].contribution, Some(queued));
}
#[test]
fn test_channel_details_splice_reorg_clears_confirmed_candidate() {
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);
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
let splice_details = |node: &Node<'_, '_, '_>| {
node.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
};
mine_transaction(&nodes[0], &splice_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
let confirmed = splice_details(&nodes[0]).unwrap().confirmed_candidate.unwrap();
assert_eq!(confirmed.txid, splice_tx.compute_txid());
assert!(confirmed.splice_locked_sent);
disconnect_blocks(&nodes[0], ANTI_REORG_DELAY);
let details = splice_details(&nodes[0]).unwrap();
assert_eq!(details.confirmed_candidate, None);
assert_eq!(details.candidates.len(), 1);
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
}
#[test]
fn test_channel_details_received_splice_locked_diverges_from_confirmed() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (original_tx, new_funding_script) =
splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
provide_utxo_reserves(&nodes, 2, added_value * 2);
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
let rbf_contribution = do_initiate_rbf_splice_in(&nodes[0], &nodes[1], channel_id, rbf_feerate);
complete_rbf_handshake(&nodes[0], &nodes[1]);
complete_interactive_funding_negotiation(
&nodes[0],
&nodes[1],
channel_id,
rbf_contribution,
new_funding_script,
);
let (rbf_tx, _) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1])
.replacing(original_tx.compute_txid()),
);
expect_splice_pending_event(&nodes[0], &node_id_1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
let splice_details = |node: &Node<'_, '_, '_>| {
node.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
};
mine_transaction(&nodes[0], &rbf_tx);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let _ = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
mine_transaction(&nodes[1], &original_tx);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let splice_locked_from_1 =
get_event_msg!(nodes[1], MessageSendEvent::SendSpliceLocked, node_id_0);
nodes[0].node.handle_splice_locked(node_id_1, &splice_locked_from_1);
let details = splice_details(&nodes[0]).unwrap();
let confirmed = details.confirmed_candidate.unwrap();
assert_eq!(confirmed.txid, rbf_tx.compute_txid());
assert!(confirmed.splice_locked_sent);
assert_eq!(details.received_splice_locked_txid, Some(original_tx.compute_txid()));
assert_ne!(Some(confirmed.txid), details.received_splice_locked_txid);
}
#[test]
fn test_channel_details_acceptor_contribution_with_queued_rbf() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution_0 = nodes[0]
.node
.splice_channel(&channel_id, &node_id_1)
.unwrap()
.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0].node.funding_contributed(&channel_id, &node_id_1, contribution_0, None).unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let contribution_1 = nodes[1]
.node
.splice_channel(&channel_id, &node_id_0)
.unwrap()
.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_1)
.unwrap();
nodes[1].node.funding_contributed(&channel_id, &node_id_0, contribution_1, None).unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(
splice_ack.funding_contribution_satoshis, 0,
"the acceptor should contribute to the counterparty's round",
);
let rbf_template = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap();
let rbf_feerate = rbf_template.min_rbf_feerate().unwrap();
let queued = rbf_template
.splice_in_sync(Amount::from_sat(25_000), rbf_feerate, FeeRate::MAX, &wallet_1)
.unwrap();
nodes[1].node.funding_contributed(&channel_id, &node_id_0, queued.clone(), None).unwrap();
let details = nodes[1]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 2);
assert!(matches!(
details.candidates[0].status,
SpliceCandidateStatus::ConstructingTransaction { is_initiator: false, .. }
));
assert!(details.candidates[0].contribution.is_some());
assert_eq!(details.candidates[1].status, SpliceCandidateStatus::WaitingOnQuiescence);
assert_eq!(details.candidates[1].contribution, Some(queued));
}
#[test]
fn test_channel_details_acceptor_contribution_reaches_signing() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 2, Amount::from_sat(100_000));
let feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64);
let wallet_0 = WalletSync::new(Arc::clone(&nodes[0].wallet_source), nodes[0].logger);
let contribution_0 = nodes[0]
.node
.splice_channel(&channel_id, &node_id_1)
.unwrap()
.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_0)
.unwrap();
nodes[0]
.node
.funding_contributed(&channel_id, &node_id_1, contribution_0.clone(), None)
.unwrap();
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let contribution_1 = nodes[1]
.node
.splice_channel(&channel_id, &node_id_0)
.unwrap()
.splice_in_sync(added_value, feerate, FeeRate::MAX, &wallet_1)
.unwrap();
nodes[1]
.node
.funding_contributed(&channel_id, &node_id_0, contribution_1.clone(), None)
.unwrap();
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
assert_ne!(
splice_ack.funding_contribution_satoshis, 0,
"the acceptor should contribute to the counterparty's round",
);
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
let new_funding_script = chan_utils::make_funding_redeemscript(
&splice_init.funding_pubkey,
&splice_ack.funding_pubkey,
)
.to_p2wsh();
complete_interactive_funding_negotiation_for_both(
&nodes[0],
&nodes[1],
channel_id,
contribution_0,
Some(contribution_1),
splice_ack.funding_contribution_satoshis,
new_funding_script,
);
let splice_details = |node: &Node<'_, '_, '_>| {
node.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap()
};
let details = splice_details(&nodes[1]);
assert_eq!(details.candidates.len(), 1);
assert!(matches!(
details.candidates[0].status,
SpliceCandidateStatus::AwaitingSignatures { is_initiator: false, .. }
));
assert!(details.candidates[0].contribution.is_some());
let (_splice_tx, splice_locked) = sign_interactive_funding_tx(
SignInteractiveFundingTxArgs::new(&nodes[0], &nodes[1]).with_acceptor_contribution(),
);
assert!(splice_locked.is_none());
expect_splice_pending_event(&nodes[0], &node_id_1);
expect_splice_pending_event(&nodes[1], &node_id_0);
let details = splice_details(&nodes[1]);
assert_eq!(details.candidates.len(), 1);
assert!(matches!(details.candidates[0].status, SpliceCandidateStatus::Negotiated { .. }));
assert!(details.candidates[0].contribution.is_some());
}
#[test]
fn test_channel_details_waiting_on_lock_below_rbf_feerate() {
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);
let node_id_0 = nodes[0].node.get_our_node_id();
let node_id_1 = nodes[1].node.get_our_node_id();
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 added_value = Amount::from_sat(50_000);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let high_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 * 4);
let wallet_1 = WalletSync::new(Arc::clone(&nodes[1].wallet_source), nodes[1].logger);
let rbf_contribution = nodes[1]
.node
.splice_channel(&channel_id, &node_id_0)
.unwrap()
.without_prior_contribution(high_feerate, FeeRate::MAX)
.with_coin_selection_source_sync(&wallet_1)
.add_value(added_value)
.unwrap()
.build()
.unwrap();
nodes[1].node.funding_contributed(&channel_id, &node_id_0, rbf_contribution, None).unwrap();
let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
nodes[0].node.handle_stfu(node_id_1, &stfu_1);
let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
nodes[1].node.handle_stfu(node_id_0, &stfu_0);
let tx_init_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxInitRbf, node_id_0);
nodes[0].node.handle_tx_init_rbf(node_id_1, &tx_init_rbf);
let _tx_ack_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxAckRbf, node_id_1);
provide_utxo_reserves(&nodes, 1, added_value * 2);
let queued = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let details = nodes[0]
.node
.list_channels()
.iter()
.find(|channel| channel.channel_id == channel_id)
.unwrap()
.splice_details
.clone()
.unwrap();
assert_eq!(details.candidates.len(), 3);
assert!(matches!(details.candidates[0].status, SpliceCandidateStatus::Negotiated { .. }));
assert_eq!(candidate_txid(&details.candidates[0]), splice_tx.compute_txid());
assert!(matches!(
details.candidates[1].status,
SpliceCandidateStatus::ConstructingTransaction { is_initiator: false, .. }
));
assert_eq!(details.candidates[2].status, SpliceCandidateStatus::WaitingOnLock);
assert_eq!(details.candidates[2].contribution, Some(queued));
nodes[0].node.get_and_clear_pending_msg_events();
nodes[1].node.get_and_clear_pending_msg_events();
}