Skip to content

Commit 4bd54f0

Browse files
committed
Test propose channel splice while disconnected
1 parent 82e9cdd commit 4bd54f0

File tree

2 files changed

+328
-14
lines changed

2 files changed

+328
-14
lines changed

lightning/src/ln/functional_test_utils.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,6 +1549,14 @@ pub fn sign_funding_transaction<'a, 'b, 'c>(
15491549
pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(
15501550
initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>,
15511551
initiator_config: Option<UserConfig>,
1552+
) -> (bitcoin::Transaction, ChannelId) {
1553+
open_zero_conf_channel_with_value(initiator, receiver, initiator_config, 100_000, 10_001)
1554+
}
1555+
1556+
// Receiver must have been initialized with manually_accept_inbound_channels set to true.
1557+
pub fn open_zero_conf_channel_with_value<'a, 'b, 'c, 'd>(
1558+
initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>,
1559+
initiator_config: Option<UserConfig>, channel_value_sat: u64, push_msat: u64,
15521560
) -> (bitcoin::Transaction, ChannelId) {
15531561
let initiator_channels = initiator.node.list_usable_channels().len();
15541562
let receiver_channels = receiver.node.list_usable_channels().len();
@@ -1558,7 +1566,7 @@ pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(
15581566

15591567
initiator
15601568
.node
1561-
.create_channel(receiver_node_id, 100_000, 10_001, 42, None, initiator_config)
1569+
.create_channel(receiver_node_id, channel_value_sat, push_msat, 42, None, initiator_config)
15621570
.unwrap();
15631571
let open_channel =
15641572
get_event_msg!(initiator, MessageSendEvent::SendOpenChannel, receiver_node_id);
@@ -1587,7 +1595,7 @@ pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(
15871595
initiator.node.handle_accept_channel(receiver_node_id, &accept_channel);
15881596

15891597
let (temporary_channel_id, tx, _) =
1590-
create_funding_transaction(&initiator, &receiver_node_id, 100_000, 42);
1598+
create_funding_transaction(&initiator, &receiver_node_id, channel_value_sat, 42);
15911599
initiator
15921600
.node
15931601
.funding_transaction_generated(temporary_channel_id, receiver_node_id, tx.clone())

lightning/src/ln/splicing_tests.rs

Lines changed: 318 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ fn test_v1_splice_in_negative_insufficient_inputs() {
6565
}
6666
}
6767

68+
fn negotiate_splice_tx_with_init<'a, 'b, 'c, 'd>(
69+
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
70+
initiator_contribution: SpliceContribution, splice_init: &msgs::SpliceInit,
71+
) -> msgs::CommitmentSigned {
72+
let node_id_initiator = initiator.node.get_our_node_id();
73+
let node_id_acceptor = acceptor.node.get_our_node_id();
74+
75+
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
76+
let splice_ack = get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
77+
initiator.node.handle_splice_ack(node_id_acceptor, &splice_ack);
78+
79+
let new_funding_script = chan_utils::make_funding_redeemscript(
80+
&splice_init.funding_pubkey,
81+
&splice_ack.funding_pubkey,
82+
)
83+
.to_p2wsh();
84+
85+
complete_interactive_funding_negotiation(
86+
initiator,
87+
acceptor,
88+
channel_id,
89+
initiator_contribution,
90+
new_funding_script,
91+
)
92+
}
93+
6894
fn negotiate_splice_tx<'a, 'b, 'c, 'd>(
6995
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
7096
initiator_contribution: SpliceContribution,
@@ -89,22 +115,12 @@ fn negotiate_splice_tx<'a, 'b, 'c, 'd>(
89115
initiator.node.handle_stfu(node_id_acceptor, &stfu_ack);
90116

91117
let splice_init = get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
92-
acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
93-
let splice_ack = get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
94-
initiator.node.handle_splice_ack(node_id_acceptor, &splice_ack);
95-
96-
let new_funding_script = chan_utils::make_funding_redeemscript(
97-
&splice_init.funding_pubkey,
98-
&splice_ack.funding_pubkey,
99-
)
100-
.to_p2wsh();
101-
102-
complete_interactive_funding_negotiation(
118+
negotiate_splice_tx_with_init(
103119
initiator,
104120
acceptor,
105121
channel_id,
106122
initiator_contribution,
107-
new_funding_script,
123+
&splice_init,
108124
)
109125
}
110126

@@ -901,3 +917,293 @@ fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
901917
.chain_source
902918
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
903919
}
920+
921+
#[test]
922+
fn test_propose_splice_while_disconnected() {
923+
do_test_propose_splice_while_disconnected(false, false);
924+
do_test_propose_splice_while_disconnected(false, true);
925+
do_test_propose_splice_while_disconnected(true, false);
926+
do_test_propose_splice_while_disconnected(true, true);
927+
}
928+
929+
fn do_test_propose_splice_while_disconnected(reload: bool, use_0conf: bool) {
930+
// Test that both nodes are able to propose a splice while the counterparty is disconnected, and
931+
// whoever doesn't go first due to the quiescence tie-breaker, will retry their splice after the
932+
// first one becomes locked.
933+
let chanmon_cfgs = create_chanmon_cfgs(2);
934+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
935+
let (persister_0a, persister_0b, persister_1a, persister_1b);
936+
let (chain_monitor_0a, chain_monitor_0b, chain_monitor_1a, chain_monitor_1b);
937+
let mut config = test_default_channel_config();
938+
if use_0conf {
939+
config.manually_accept_inbound_channels = true;
940+
config.channel_handshake_limits.trust_own_funding_0conf = true;
941+
}
942+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
943+
let (node_0a, node_0b, node_1a, node_1b);
944+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
945+
946+
let node_id_0 = nodes[0].node.get_our_node_id();
947+
let node_id_1 = nodes[1].node.get_our_node_id();
948+
949+
let initial_channel_value_sat = 1_000_000;
950+
let push_msat = initial_channel_value_sat / 2 * 1000;
951+
let channel_id = if use_0conf {
952+
let (funding_tx, channel_id) = open_zero_conf_channel_with_value(
953+
&nodes[0],
954+
&nodes[1],
955+
None,
956+
initial_channel_value_sat,
957+
push_msat,
958+
);
959+
mine_transaction(&nodes[0], &funding_tx);
960+
mine_transaction(&nodes[1], &funding_tx);
961+
channel_id
962+
} else {
963+
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
964+
&nodes,
965+
0,
966+
1,
967+
initial_channel_value_sat,
968+
push_msat,
969+
);
970+
channel_id
971+
};
972+
973+
// Start with the nodes disconnected, and have each one attempt a splice.
974+
nodes[0].node.peer_disconnected(node_id_1);
975+
nodes[1].node.peer_disconnected(node_id_0);
976+
977+
let splice_out_sat = initial_channel_value_sat / 4;
978+
let node_0_contribution = SpliceContribution::SpliceOut {
979+
outputs: vec![TxOut {
980+
value: Amount::from_sat(splice_out_sat),
981+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
982+
}],
983+
};
984+
nodes[0]
985+
.node
986+
.splice_channel(
987+
&channel_id,
988+
&node_id_1,
989+
node_0_contribution.clone(),
990+
FEERATE_FLOOR_SATS_PER_KW,
991+
None,
992+
)
993+
.unwrap();
994+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
995+
996+
let node_1_contribution = SpliceContribution::SpliceOut {
997+
outputs: vec![TxOut {
998+
value: Amount::from_sat(splice_out_sat),
999+
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
1000+
}],
1001+
};
1002+
nodes[1]
1003+
.node
1004+
.splice_channel(
1005+
&channel_id,
1006+
&node_id_0,
1007+
node_1_contribution.clone(),
1008+
FEERATE_FLOOR_SATS_PER_KW,
1009+
None,
1010+
)
1011+
.unwrap();
1012+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
1013+
1014+
if reload {
1015+
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
1016+
reload_node!(
1017+
nodes[0],
1018+
nodes[0].node.encode(),
1019+
&[&encoded_monitor_0],
1020+
persister_0a,
1021+
chain_monitor_0a,
1022+
node_0a
1023+
);
1024+
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
1025+
reload_node!(
1026+
nodes[1],
1027+
nodes[1].node.encode(),
1028+
&[&encoded_monitor_1],
1029+
persister_1a,
1030+
chain_monitor_1a,
1031+
node_1a
1032+
);
1033+
}
1034+
1035+
// Reconnect the nodes. Both nodes should attempt quiescence as the initiator, but only one will
1036+
// be it via the tie-breaker.
1037+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
1038+
reconnect_args.send_channel_ready = (true, true);
1039+
if !use_0conf {
1040+
reconnect_args.send_announcement_sigs = (true, true);
1041+
}
1042+
reconnect_args.send_stfu = (true, true);
1043+
reconnect_nodes(reconnect_args);
1044+
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
1045+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
1046+
1047+
let (prev_funding_outpoint, prev_funding_script) = nodes[0]
1048+
.chain_monitor
1049+
.chain_monitor
1050+
.get_monitor(channel_id)
1051+
.map(|monitor| (monitor.get_funding_txo(), monitor.get_funding_script()))
1052+
.unwrap();
1053+
1054+
// Negotiate the first splice to completion.
1055+
let initial_commit_sig = negotiate_splice_tx_with_init(
1056+
&nodes[0],
1057+
&nodes[1],
1058+
channel_id,
1059+
node_0_contribution,
1060+
&splice_init,
1061+
);
1062+
let (splice_tx, splice_locked) =
1063+
sign_interactive_funding_tx(&nodes[0], &nodes[1], initial_commit_sig, use_0conf);
1064+
1065+
let splice_locked = if use_0conf {
1066+
let (splice_locked, for_node_id) = splice_locked.unwrap();
1067+
assert_eq!(for_node_id, node_id_1);
1068+
splice_locked
1069+
} else {
1070+
assert!(splice_locked.is_none());
1071+
1072+
mine_transaction(&nodes[0], &splice_tx);
1073+
mine_transaction(&nodes[1], &splice_tx);
1074+
1075+
// Mine enough blocks for the first splice to become locked.
1076+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
1077+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
1078+
1079+
get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1)
1080+
};
1081+
nodes[1].node.handle_splice_locked(node_id_0, &splice_locked);
1082+
1083+
// We should see the node which lost the tie-breaker attempt their splice now by first
1084+
// negotiating quiescence, but their `stfu` won't be sent until after another reconnection.
1085+
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
1086+
assert_eq!(msg_events.len(), if use_0conf { 2 } else { 3 }, "{msg_events:?}");
1087+
if let MessageSendEvent::SendSpliceLocked { ref msg, .. } = &msg_events[0] {
1088+
nodes[0].node.handle_splice_locked(node_id_1, msg);
1089+
if use_0conf {
1090+
// TODO(splicing): Revisit splice transaction rebroadcasts.
1091+
let txn_0 = nodes[0].tx_broadcaster.txn_broadcast();
1092+
assert_eq!(txn_0.len(), 1);
1093+
assert_eq!(&txn_0[0], &splice_tx);
1094+
mine_transaction(&nodes[0], &splice_tx);
1095+
mine_transaction(&nodes[1], &splice_tx);
1096+
}
1097+
} else {
1098+
panic!("Unexpected event {:?}", &msg_events[0]);
1099+
}
1100+
if !use_0conf {
1101+
if let MessageSendEvent::SendAnnouncementSignatures { ref msg, .. } = &msg_events[1] {
1102+
nodes[0].node.handle_announcement_signatures(node_id_1, msg);
1103+
} else {
1104+
panic!("Unexpected event {:?}", &msg_events[1]);
1105+
}
1106+
}
1107+
assert!(matches!(
1108+
&msg_events[if use_0conf { 1 } else { 2 }],
1109+
MessageSendEvent::SendStfu { .. }
1110+
));
1111+
1112+
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
1113+
assert_eq!(msg_events.len(), if use_0conf { 0 } else { 2 }, "{msg_events:?}");
1114+
if !use_0conf {
1115+
if let MessageSendEvent::SendAnnouncementSignatures { ref msg, .. } = &msg_events[0] {
1116+
nodes[1].node.handle_announcement_signatures(node_id_0, msg);
1117+
} else {
1118+
panic!("Unexpected event {:?}", &msg_events[1]);
1119+
}
1120+
assert!(matches!(&msg_events[1], MessageSendEvent::BroadcastChannelAnnouncement { .. }));
1121+
}
1122+
1123+
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
1124+
assert_eq!(msg_events.len(), if use_0conf { 0 } else { 1 }, "{msg_events:?}");
1125+
if !use_0conf {
1126+
assert!(matches!(&msg_events[0], MessageSendEvent::BroadcastChannelAnnouncement { .. }));
1127+
}
1128+
1129+
expect_channel_ready_event(&nodes[0], &node_id_1);
1130+
check_added_monitors(&nodes[0], 1);
1131+
expect_channel_ready_event(&nodes[1], &node_id_0);
1132+
check_added_monitors(&nodes[1], 1);
1133+
1134+
// Remove the corresponding outputs and transactions the chain source is watching for the
1135+
// old funding as it is no longer being tracked.
1136+
nodes[0]
1137+
.chain_source
1138+
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
1139+
nodes[1]
1140+
.chain_source
1141+
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
1142+
1143+
// Reconnect the nodes. This should trigger the node which lost the tie-breaker to resend `stfu`
1144+
// for their splice attempt.
1145+
if reload {
1146+
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
1147+
reload_node!(
1148+
nodes[0],
1149+
nodes[0].node.encode(),
1150+
&[&encoded_monitor_0],
1151+
persister_0b,
1152+
chain_monitor_0b,
1153+
node_0b
1154+
);
1155+
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
1156+
reload_node!(
1157+
nodes[1],
1158+
nodes[1].node.encode(),
1159+
&[&encoded_monitor_1],
1160+
persister_1b,
1161+
chain_monitor_1b,
1162+
node_1b
1163+
);
1164+
} else {
1165+
nodes[0].node.peer_disconnected(node_id_1);
1166+
nodes[1].node.peer_disconnected(node_id_0);
1167+
}
1168+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
1169+
reconnect_args.send_channel_ready = (true, true);
1170+
if !use_0conf {
1171+
reconnect_args.send_announcement_sigs = (true, true);
1172+
}
1173+
reconnect_args.send_stfu = (true, false);
1174+
reconnect_nodes(reconnect_args);
1175+
1176+
// Drive the second splice to completion.
1177+
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
1178+
assert_eq!(msg_events.len(), 1, "{msg_events:?}");
1179+
if let MessageSendEvent::SendStfu { ref msg, .. } = msg_events[0] {
1180+
nodes[1].node.handle_stfu(node_id_0, msg);
1181+
} else {
1182+
panic!("Unexpected event {:?}", &msg_events[0]);
1183+
}
1184+
1185+
let splice_init = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceInit, node_id_0);
1186+
let initial_commit_sig = negotiate_splice_tx_with_init(
1187+
&nodes[1],
1188+
&nodes[0],
1189+
channel_id,
1190+
node_1_contribution,
1191+
&splice_init,
1192+
);
1193+
let (splice_tx, splice_locked) =
1194+
sign_interactive_funding_tx(&nodes[1], &nodes[0], initial_commit_sig, use_0conf);
1195+
1196+
if use_0conf {
1197+
let (splice_locked, for_node_id) = splice_locked.unwrap();
1198+
assert_eq!(for_node_id, node_id_0);
1199+
lock_splice(&nodes[1], &nodes[0], &splice_locked, true);
1200+
} else {
1201+
assert!(splice_locked.is_none());
1202+
mine_transaction(&nodes[0], &splice_tx);
1203+
mine_transaction(&nodes[1], &splice_tx);
1204+
lock_splice_after_blocks(&nodes[1], &nodes[0], ANTI_REORG_DELAY - 1);
1205+
}
1206+
1207+
// Sanity check that we can still make a test payment.
1208+
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
1209+
}

0 commit comments

Comments
 (0)