Skip to content

Commit 5b25a09

Browse files
committed
Add test_0reserve_splice
1 parent 540109b commit 5b25a09

2 files changed

Lines changed: 353 additions & 2 deletions

File tree

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,7 @@ fn test_0reserve_no_outputs() {
25812581
do_test_0reserve_no_outputs_p2a_anchor();
25822582
}
25832583

2584-
fn setup_0reserve_no_outputs_channels<'a, 'b, 'c, 'd>(
2584+
pub(crate) fn setup_0reserve_no_outputs_channels<'a, 'b, 'c, 'd>(
25852585
nodes: &'a Vec<Node<'b, 'c, 'd>>, channel_value_sat: u64, dust_limit_satoshis: u64,
25862586
) -> (ChannelId, Transaction) {
25872587
let node_a_id = nodes[0].node.get_our_node_id();

lightning/src/ln/splicing_tests.rs

Lines changed: 352 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::chain::ChannelMonitorUpdateStatus;
1616
use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};
1717
use crate::ln::chan_utils;
1818
use crate::ln::channel::{
19-
CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE,
19+
ANCHOR_OUTPUT_VALUE_SATOSHI, CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY,
20+
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE,
2021
};
2122
use crate::ln::channelmanager::{provided_init_features, PaymentId, BREAKDOWN_TIMEOUT};
2223
use crate::ln::functional_test_utils::*;
@@ -6857,3 +6858,353 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
68576858
other => panic!("Expected SpliceFailed, got {:?}", other),
68586859
}
68596860
}
6861+
6862+
#[test]
6863+
fn test_0reserve_splice() {
6864+
let mut config = test_default_channel_config();
6865+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
6866+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
6867+
let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone());
6868+
let _b = do_test_0reserve_splice_holder_validation(true, false, false, config.clone());
6869+
let _c = do_test_0reserve_splice_holder_validation(false, true, false, config.clone());
6870+
let _d = do_test_0reserve_splice_holder_validation(true, true, false, config.clone());
6871+
6872+
let _e = do_test_0reserve_splice_holder_validation(false, false, true, config.clone());
6873+
let _f = do_test_0reserve_splice_holder_validation(true, false, true, config.clone());
6874+
let _g = do_test_0reserve_splice_holder_validation(false, true, true, config.clone());
6875+
let _h = do_test_0reserve_splice_holder_validation(true, true, true, config.clone());
6876+
6877+
assert_eq!(a, ChannelTypeFeatures::only_static_remote_key());
6878+
6879+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
6880+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
6881+
let a = do_test_0reserve_splice_holder_validation(false, false, false, config.clone());
6882+
let _b = do_test_0reserve_splice_holder_validation(true, false, false, config.clone());
6883+
let _c = do_test_0reserve_splice_holder_validation(false, true, false, config.clone());
6884+
let _d = do_test_0reserve_splice_holder_validation(true, true, false, config.clone());
6885+
6886+
let _e = do_test_0reserve_splice_holder_validation(false, false, true, config.clone());
6887+
let _f = do_test_0reserve_splice_holder_validation(true, false, true, config.clone());
6888+
let _g = do_test_0reserve_splice_holder_validation(false, true, true, config.clone());
6889+
let _h = do_test_0reserve_splice_holder_validation(true, true, true, config.clone());
6890+
6891+
assert_eq!(a, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
6892+
6893+
let mut config = test_default_channel_config();
6894+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
6895+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
6896+
let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone());
6897+
let _b = do_test_0reserve_splice_counterparty_validation(true, false, false, config.clone());
6898+
let _c = do_test_0reserve_splice_counterparty_validation(false, true, false, config.clone());
6899+
let _d = do_test_0reserve_splice_counterparty_validation(true, true, false, config.clone());
6900+
6901+
let _e = do_test_0reserve_splice_counterparty_validation(false, false, true, config.clone());
6902+
let _f = do_test_0reserve_splice_counterparty_validation(true, false, true, config.clone());
6903+
let _g = do_test_0reserve_splice_counterparty_validation(false, true, true, config.clone());
6904+
let _h = do_test_0reserve_splice_counterparty_validation(true, true, true, config.clone());
6905+
assert_eq!(a, ChannelTypeFeatures::only_static_remote_key());
6906+
6907+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
6908+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
6909+
let a = do_test_0reserve_splice_counterparty_validation(false, false, false, config.clone());
6910+
let _b = do_test_0reserve_splice_counterparty_validation(true, false, false, config.clone());
6911+
let _c = do_test_0reserve_splice_counterparty_validation(false, true, false, config.clone());
6912+
let _d = do_test_0reserve_splice_counterparty_validation(true, true, false, config.clone());
6913+
6914+
let _e = do_test_0reserve_splice_counterparty_validation(false, false, true, config.clone());
6915+
let _f = do_test_0reserve_splice_counterparty_validation(true, false, true, config.clone());
6916+
let _g = do_test_0reserve_splice_counterparty_validation(false, true, true, config.clone());
6917+
let _h = do_test_0reserve_splice_counterparty_validation(true, true, true, config.clone());
6918+
assert_eq!(a, ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies());
6919+
6920+
// TODO: Skip 0FC channels for now as these always have an output on the commitment, the P2A
6921+
// output. We will be able to withdraw up to the dust limit of the funding script, which
6922+
// is checked in interactivetx. Still need to double check whether that's what we actually
6923+
// want.
6924+
}
6925+
6926+
#[cfg(test)]
6927+
fn do_test_0reserve_splice_holder_validation(
6928+
splice_passes: bool, counterparty_has_output: bool, node_0_is_initiator: bool,
6929+
mut config: UserConfig,
6930+
) -> ChannelTypeFeatures {
6931+
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
6932+
6933+
let chanmon_cfgs = create_chanmon_cfgs(2);
6934+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
6935+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
6936+
100;
6937+
let node_chanmgrs =
6938+
create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config.clone())]);
6939+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
6940+
6941+
let _node_id_0 = nodes[0].node.get_our_node_id();
6942+
let _node_id_1 = nodes[1].node.get_our_node_id();
6943+
6944+
let channel_value_sat = 100_000;
6945+
// Some dust limit, does not matter
6946+
let dust_limit_satoshis = 546;
6947+
6948+
let (channel_id, _tx) =
6949+
setup_0reserve_no_outputs_channels(&nodes, channel_value_sat, dust_limit_satoshis);
6950+
let details = &nodes[0].node.list_channels()[0];
6951+
let channel_type = details.channel_type.clone().unwrap();
6952+
6953+
let feerate = 253;
6954+
let spiked_feerate = if channel_type == ChannelTypeFeatures::only_static_remote_key() {
6955+
feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
6956+
} else if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
6957+
feerate
6958+
} else {
6959+
panic!("Unexpected channel type");
6960+
};
6961+
let anchors_sat =
6962+
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
6963+
ANCHOR_OUTPUT_VALUE_SATOSHI * 2
6964+
} else {
6965+
0
6966+
};
6967+
6968+
let initiator_value_to_self_sat = if counterparty_has_output {
6969+
send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000);
6970+
channel_value_sat / 2
6971+
} else if !node_0_is_initiator {
6972+
let tx_fee_msat = chan_utils::commit_tx_fee_sat(spiked_feerate, 2, &channel_type) * 1000;
6973+
let node_0_details = &nodes[0].node.list_channels()[0];
6974+
let outbound_capacity_msat = node_0_details.outbound_capacity_msat;
6975+
let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat;
6976+
assert_eq!(outbound_capacity_msat, (channel_value_sat - anchors_sat) * 1000);
6977+
assert_eq!(available_capacity_msat, outbound_capacity_msat - tx_fee_msat);
6978+
send_payment(&nodes[0], &[&nodes[1]], available_capacity_msat);
6979+
6980+
// Make sure node 0 has no output on the commitment at this point
6981+
let node_0_to_local_output_msat = channel_value_sat * 1000
6982+
- available_capacity_msat
6983+
- anchors_sat * 1000
6984+
- chan_utils::commit_tx_fee_sat(feerate, 0, &channel_type) * 1000;
6985+
assert!(node_0_to_local_output_msat / 1000 < dust_limit_satoshis);
6986+
let commit_tx = &get_local_commitment_txn!(nodes[0], channel_id)[0];
6987+
assert_eq!(commit_tx.output.len(), if anchors_sat == 0 { 1 } else { 2 });
6988+
assert_eq!(
6989+
commit_tx.output.last().unwrap().value,
6990+
Amount::from_sat(available_capacity_msat / 1000)
6991+
);
6992+
if anchors_sat != 0 {
6993+
assert_eq!(commit_tx.output[0].value, Amount::from_sat(330));
6994+
}
6995+
6996+
available_capacity_msat / 1000
6997+
} else {
6998+
channel_value_sat
6999+
};
7000+
7001+
// The estimated fees to splice out a single output at 253sat/kw
7002+
let estimated_fees = 183;
7003+
let splice_out_max_value = if counterparty_has_output && node_0_is_initiator {
7004+
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 1, &channel_type);
7005+
Amount::from_sat(
7006+
initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - estimated_fees,
7007+
)
7008+
} else if !counterparty_has_output && node_0_is_initiator {
7009+
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type);
7010+
Amount::from_sat(
7011+
initiator_value_to_self_sat
7012+
- commit_tx_fee_sat
7013+
- anchors_sat - estimated_fees
7014+
- dust_limit_satoshis,
7015+
)
7016+
} else if counterparty_has_output && !node_0_is_initiator {
7017+
Amount::from_sat(initiator_value_to_self_sat - estimated_fees)
7018+
} else if !counterparty_has_output && !node_0_is_initiator {
7019+
Amount::from_sat(initiator_value_to_self_sat - estimated_fees - dust_limit_satoshis)
7020+
} else {
7021+
panic!("unexpected case!");
7022+
};
7023+
let outputs = vec![TxOut {
7024+
value: splice_out_max_value + if splice_passes { Amount::ZERO } else { Amount::ONE_SAT },
7025+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
7026+
}];
7027+
7028+
let (initiator, acceptor) =
7029+
if node_0_is_initiator { (&nodes[0], &nodes[1]) } else { (&nodes[1], &nodes[0]) };
7030+
7031+
let initiator_details = &initiator.node.list_channels()[0];
7032+
assert_eq!(
7033+
initiator_details.next_splice_out_maximum_sat,
7034+
splice_out_max_value.to_sat() + estimated_fees
7035+
);
7036+
7037+
if splice_passes {
7038+
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
7039+
7040+
let (splice_tx, _) = splice_channel(initiator, acceptor, channel_id, contribution);
7041+
mine_transaction(initiator, &splice_tx);
7042+
mine_transaction(acceptor, &splice_tx);
7043+
lock_splice_after_blocks(initiator, acceptor, ANTI_REORG_DELAY - 1);
7044+
} else {
7045+
assert!(initiate_splice_out(initiator, acceptor, channel_id, outputs).is_err());
7046+
let splice_out_value =
7047+
splice_out_max_value + Amount::from_sat(estimated_fees) + Amount::ONE_SAT;
7048+
let splice_out_max_value = splice_out_max_value + Amount::from_sat(estimated_fees);
7049+
let cannot_be_funded = format!(
7050+
"Channel {channel_id} cannot be funded: Our \
7051+
splice-out value of {splice_out_value} is greater than the maximum \
7052+
{splice_out_max_value}"
7053+
);
7054+
initiator.logger.assert_log("lightning::ln::channel", cannot_be_funded, 1);
7055+
}
7056+
7057+
channel_type
7058+
}
7059+
7060+
#[cfg(test)]
7061+
fn do_test_0reserve_splice_counterparty_validation(
7062+
splice_passes: bool, counterparty_has_output: bool, node_0_is_initiator: bool,
7063+
mut config: UserConfig,
7064+
) -> ChannelTypeFeatures {
7065+
use crate::ln::htlc_reserve_unit_tests::setup_0reserve_no_outputs_channels;
7066+
7067+
let chanmon_cfgs = create_chanmon_cfgs(2);
7068+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
7069+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
7070+
100;
7071+
let node_chanmgrs =
7072+
create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config.clone())]);
7073+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
7074+
7075+
let _node_id_0 = nodes[0].node.get_our_node_id();
7076+
let _node_id_1 = nodes[1].node.get_our_node_id();
7077+
7078+
let channel_value_sat = 100_000;
7079+
// Some dust limit, does not matter
7080+
let dust_limit_satoshis = 546;
7081+
7082+
let (channel_id, _tx) =
7083+
setup_0reserve_no_outputs_channels(&nodes, channel_value_sat, dust_limit_satoshis);
7084+
let details = &nodes[0].node.list_channels()[0];
7085+
let channel_type = details.channel_type.clone().unwrap();
7086+
7087+
let feerate = 253;
7088+
let spiked_feerate = if channel_type == ChannelTypeFeatures::only_static_remote_key() {
7089+
feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
7090+
} else if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
7091+
feerate
7092+
} else {
7093+
panic!("Unexpected channel type");
7094+
};
7095+
let anchors_sat =
7096+
if channel_type == ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() {
7097+
ANCHOR_OUTPUT_VALUE_SATOSHI * 2
7098+
} else {
7099+
0
7100+
};
7101+
7102+
let initiator_value_to_self_sat = if counterparty_has_output {
7103+
send_payment(&nodes[0], &[&nodes[1]], channel_value_sat / 2 * 1_000);
7104+
channel_value_sat / 2
7105+
} else if !node_0_is_initiator {
7106+
let tx_fee_msat = chan_utils::commit_tx_fee_sat(spiked_feerate, 2, &channel_type) * 1000;
7107+
let node_0_details = &nodes[0].node.list_channels()[0];
7108+
let outbound_capacity_msat = node_0_details.outbound_capacity_msat;
7109+
let available_capacity_msat = node_0_details.next_outbound_htlc_limit_msat;
7110+
assert_eq!(outbound_capacity_msat, (channel_value_sat - anchors_sat) * 1000);
7111+
assert_eq!(available_capacity_msat, outbound_capacity_msat - tx_fee_msat);
7112+
send_payment(&nodes[0], &[&nodes[1]], available_capacity_msat);
7113+
7114+
// Make sure node 0 has no output on the commitment at this point
7115+
let node_0_to_local_output_msat = channel_value_sat * 1000
7116+
- available_capacity_msat
7117+
- anchors_sat * 1000
7118+
- chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type) * 1000;
7119+
assert!(node_0_to_local_output_msat / 1000 < dust_limit_satoshis);
7120+
let commit_tx = &get_local_commitment_txn!(nodes[0], channel_id)[0];
7121+
assert_eq!(commit_tx.output.len(), if anchors_sat == 0 { 1 } else { 2 });
7122+
assert_eq!(
7123+
commit_tx.output.last().unwrap().value,
7124+
Amount::from_sat(available_capacity_msat / 1000)
7125+
);
7126+
if anchors_sat != 0 {
7127+
assert_eq!(commit_tx.output[0].value, Amount::from_sat(330));
7128+
}
7129+
7130+
available_capacity_msat / 1000
7131+
} else {
7132+
channel_value_sat
7133+
};
7134+
7135+
let splice_out_value_incl_fees = if counterparty_has_output && node_0_is_initiator {
7136+
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 1, &channel_type);
7137+
Amount::from_sat(initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat)
7138+
} else if !counterparty_has_output && node_0_is_initiator {
7139+
let commit_tx_fee_sat = chan_utils::commit_tx_fee_sat(spiked_feerate, 0, &channel_type);
7140+
Amount::from_sat(
7141+
initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - dust_limit_satoshis,
7142+
)
7143+
} else if counterparty_has_output && !node_0_is_initiator {
7144+
Amount::from_sat(initiator_value_to_self_sat)
7145+
} else if !counterparty_has_output && !node_0_is_initiator {
7146+
Amount::from_sat(initiator_value_to_self_sat - dust_limit_satoshis)
7147+
} else {
7148+
panic!("unexpected case!");
7149+
};
7150+
7151+
let (initiator, acceptor) =
7152+
if node_0_is_initiator { (&nodes[0], &nodes[1]) } else { (&nodes[1], &nodes[0]) };
7153+
7154+
let initiator_details = &initiator.node.list_channels()[0];
7155+
assert_eq!(initiator_details.next_splice_out_maximum_sat, splice_out_value_incl_fees.to_sat());
7156+
7157+
let funding_contribution_sat =
7158+
-(splice_out_value_incl_fees.to_sat() as i64) - if splice_passes { 0 } else { 1 };
7159+
let outputs = vec![TxOut {
7160+
// Splice out some dummy amount to get past the initiator's validation,
7161+
// we'll modify the message in-flight.
7162+
value: Amount::from_sat(1_000),
7163+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
7164+
}];
7165+
let _contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
7166+
7167+
let node_id_initiator = initiator.node.get_our_node_id();
7168+
let node_id_acceptor = acceptor.node.get_our_node_id();
7169+
7170+
let stfu_init = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
7171+
acceptor.node.handle_stfu(node_id_initiator, &stfu_init);
7172+
let stfu_ack = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
7173+
initiator.node.handle_stfu(node_id_acceptor, &stfu_ack);
7174+
7175+
let mut splice_init =
7176+
get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
7177+
// Make the modification here
7178+
splice_init.funding_contribution_satoshis = funding_contribution_sat;
7179+
7180+
if splice_passes {
7181+
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
7182+
let _splice_ack =
7183+
get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
7184+
} else {
7185+
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
7186+
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
7187+
assert_eq!(msg_events.len(), 1);
7188+
if let MessageSendEvent::HandleError { action, .. } = &msg_events[0] {
7189+
assert!(matches!(action, msgs::ErrorAction::DisconnectPeerWithWarning { .. }));
7190+
} else {
7191+
panic!("Expected MessageSendEvent::HandleError");
7192+
}
7193+
let cannot_splice_out = if u64::try_from(funding_contribution_sat.abs()).unwrap()
7194+
> initiator_value_to_self_sat
7195+
{
7196+
format!(
7197+
"Got non-closing error: Their contribution candidate {funding_contribution_sat}sat \
7198+
is greater than their total balance in the channel {initiator_value_to_self_sat}sat"
7199+
)
7200+
} else {
7201+
format!(
7202+
"Got non-closing error: Channel {channel_id} cannot \
7203+
be spliced; Balance exhausted on local commitment"
7204+
)
7205+
};
7206+
acceptor.logger.assert_log("lightning::ln::channelmanager", cannot_splice_out, 1);
7207+
}
7208+
7209+
channel_type
7210+
}

0 commit comments

Comments
 (0)