Capture stfu send in reconnection tests
What changed, and why it matters
This commit only changes test code. It updates a test helper macro and related reconnection tests to capture and optionally handle 'stfu' messages sent when Lightning channels reconnect. There is no change to production code, so it does not introduce or fix a security vulnerability on its own.
No security action needed; treat as routine test infrastructure change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies lightning/src/ln/functional_test_utils.rs and lightning/src/ln/async_signer_tests.rs. It extends the handle_chan_reestablish_msgs! macro to detect MessageSendEvent::SendStfu, adds a send_stfu field to ReconnectArgs, and updates reconnect_nodes to optionally deliver captured stfu messages. Existing test call sites are updated to account for the new tuple element. No runtime logic is altered.
Changed components
lightning/src/ln/functional_test_utils.rs (test utilities only)lightning/src/ln/async_signer_tests.rs (tests only)Inspect captured patch +43 / −7
diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs
index de5103a..7182108 100644
--- a/lightning/src/ln/async_signer_tests.rs
+++ b/lightning/src/ln/async_signer_tests.rs
@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}
// Expect the RAA
- let (_, revoke_and_ack, commitment_signed, resend_order, _, _) =
+ let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
- let (_, revoke_and_ack, commitment_signed, resend_order, _, _) =
+ let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
- let (_, revoke_and_ack, commitment_signed, _, _, _) =
+ let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
@@ -746,7 +746,8 @@ fn do_test_async_commitment_signature_peer_disconnect(
}
// Expect the RAA
- let (_, revoke_and_ack, commitment_signed, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
+ let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
+ handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(commitment_signed.is_none());
@@ -759,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
- let (_, _, commitment_signed, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
+ let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
- let (_, _, commitment_signed, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
+ let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
@@ -880,6 +881,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.2.is_none());
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
+ assert!(as_resp.6.is_none());
if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
@@ -901,6 +903,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.2.is_none());
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
+ assert!(as_resp.6.is_none());
nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
@@ -923,6 +926,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.5.is_none());
assert!(bs_resp.5.is_none());
+ assert!(as_resp.6.is_none());
+ assert!(bs_resp.6.is_none());
+
// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index 9845d6d..3d7d69c 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -4871,6 +4871,13 @@ macro_rules! handle_chan_reestablish_msgs {
had_channel_update = true;
}
+ let mut stfu = None;
+ if let Some(&MessageSendEvent::SendStfu { ref node_id, ref msg }) = msg_events.get(idx) {
+ idx += 1;
+ assert_eq!(*node_id, $dst_node.node.get_our_node_id());
+ stfu = Some(msg.clone());
+ }
+
let mut revoke_and_ack = None;
let mut commitment_update = None;
let order = if let Some(ev) = msg_events.get(idx) {
@@ -4946,7 +4953,15 @@ macro_rules! handle_chan_reestablish_msgs {
assert_eq!(msg_events.len(), idx, "{msg_events:?}");
- (channel_ready, revoke_and_ack, commitment_update, order, announcement_sigs, tx_signatures)
+ (
+ channel_ready,
+ revoke_and_ack,
+ commitment_update,
+ order,
+ announcement_sigs,
+ tx_signatures,
+ stfu,
+ )
}};
}
@@ -4955,6 +4970,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub node_b: &'a Node<'b, 'c, 'd>,
pub send_channel_ready: (bool, bool),
pub send_announcement_sigs: (bool, bool),
+ pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
@@ -4977,6 +4993,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
node_b,
send_channel_ready: (false, false),
send_announcement_sigs: (false, false),
+ send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
@@ -5000,6 +5017,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
node_b,
send_channel_ready,
send_announcement_sigs,
+ send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
expect_renegotiated_funding_locked_monitor_update,
@@ -5118,6 +5136,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
} else {
assert!(chan_msgs.4.is_none());
}
+ if send_stfu.0 {
+ let stfu = chan_msgs.6.take().unwrap();
+ node_a.node.handle_stfu(node_b_id, &stfu);
+ } else {
+ assert!(chan_msgs.6.is_none());
+ }
if send_interactive_tx_commit_sig.0 {
assert!(chan_msgs.1.is_none());
let commitment_update = chan_msgs.2.take().unwrap();
@@ -5224,6 +5248,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
} else {
assert!(chan_msgs.4.is_none());
}
+ if send_stfu.1 {
+ let stfu = chan_msgs.6.take().unwrap();
+ node_b.node.handle_stfu(node_a_id, &stfu);
+ } else {
+ assert!(chan_msgs.6.is_none());
+ }
if send_interactive_tx_commit_sig.1 {
assert!(chan_msgs.1.is_none());
let commitment_update = chan_msgs.2.take().unwrap();
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.