Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14636,7 +14636,24 @@ where
return None;
}

if let Some(action) = self.quiescent_action.as_ref() {
if self.context.is_waiting_on_peer_pending_channel_update()
|| self.context.is_monitor_or_signer_pending_channel_update()
{
log_given_level!(
logger,
logger_level,
"Waiting for state machine pending changes to complete before sending stfu"
);
return None;
}

let initiator = if self.context.channel_state.is_remote_stfu_sent() {
// Since we may have also attempted to initiate quiescence but the counterparty
// initiated first, we'll retry after we're no longer quiescent.
Comment on lines +14651 to +14652
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, we would attempt to contribute as an acceptor, assuming we are both initiating for a splice.

self.context.channel_state.clear_remote_stfu_sent();
self.context.channel_state.set_quiescent();
false
} else if let Some(action) = self.quiescent_action.as_ref() {
#[allow(irrefutable_let_patterns)]
if let QuiescentAction::Splice { contribution, .. } = action {
if self.pending_splice.is_some() {
Expand All @@ -14663,29 +14680,16 @@ where
}
}
}
}

if self.context.is_waiting_on_peer_pending_channel_update()
|| self.context.is_monitor_or_signer_pending_channel_update()
{
log_given_level!(
logger,
logger_level,
"Waiting for state machine pending changes to complete before sending stfu"
);
return None;
}

let initiator = if self.context.channel_state.is_remote_stfu_sent() {
// Since we may have also attempted to initiate quiescence but the counterparty
// initiated first, we'll retry after we're no longer quiescent.
self.context.channel_state.clear_remote_stfu_sent();
self.context.channel_state.set_quiescent();
false
} else {
log_debug!(logger, "Sending stfu as quiescence initiator");
self.context.channel_state.set_local_stfu_sent();
true
} else {
debug_assert!(
false,
"Either we have a pending quiescent action or need to respond to the counterparty"
);
false
};

Some(msgs::Stfu { channel_id: self.context.channel_id, initiator })
Expand Down
47 changes: 47 additions & 0 deletions lightning/src/ln/splicing_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7841,6 +7841,53 @@ fn test_funding_contributed_rbf_adjustment_exceeds_max_feerate() {
assert_eq!(splice_init.funding_feerate_per_kw, FEERATE_FLOOR_SATS_PER_KW);
}

#[test]
fn test_peer_initiated_stfu_skips_local_rbf_feerate_check() {
// Test that a local low-fee splice RBF attempt does not prevent us from responding to a
// counterparty-initiated quiescence attempt.
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();

// Node 1 creates a pending splice before node 0 submits its contribution. Node 0's
// contribution cannot be adjusted up to the pending splice's minimum RBF feerate, so it must
// not send its own stfu yet.
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());

// Node 1 can still initiate quiescence for its own RBF attempt. Node 0 should reply as the
// non-initiator instead of applying its local splice RBF feerate check to the response.
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() {
// Test that when the change output can't absorb the fee increase needed for the minimum RBF feerate
Expand Down
Loading