@@ -2183,6 +2183,124 @@ fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
21832183 . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script) ;
21842184}
21852185
2186+ #[ test]
2187+ fn test_splice_confirms_on_both_sides_while_disconnected ( ) {
2188+ // Regression test: when a splice transaction confirms on both sides while peers are
2189+ // disconnected, each peer's `channel_reestablish` carries `my_current_funding_locked` with the
2190+ // splice txid. The receiving side must not emit `announcement_signatures` for the pre-splice
2191+ // funding in that handler — those would be verified against the post-splice channel
2192+ // announcement on the peer and force-close the channel. Instead, sigs are generated after the
2193+ // inferred `splice_locked` promotes the splice funding.
2194+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
2195+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
2196+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
2197+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
2198+
2199+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
2200+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
2201+
2202+ let initial_channel_value_sat = 100_000 ;
2203+ let ( _, _, channel_id, _) =
2204+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
2205+
2206+ let prev_funding_outpoint = get_monitor ! ( nodes[ 0 ] , channel_id) . get_funding_txo ( ) ;
2207+ let prev_funding_script = get_monitor ! ( nodes[ 0 ] , channel_id) . get_funding_script ( ) ;
2208+
2209+ // Capture the pre-splice scid so we can later assert the announcement_sigs each side emits
2210+ // on reconnect carry the post-splice scid, not the pre-splice one the bug would emit.
2211+ let pre_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2212+
2213+ let outputs = vec ! [
2214+ TxOut {
2215+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2216+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2217+ } ,
2218+ TxOut {
2219+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2220+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2221+ } ,
2222+ ] ;
2223+ let funding_contribution =
2224+ initiate_splice_out ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, outputs) . unwrap ( ) ;
2225+ let ( splice_tx, _) = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
2226+
2227+ // Disconnect before either side confirms the splice.
2228+ nodes[ 0 ] . node . peer_disconnected ( node_id_1) ;
2229+ nodes[ 1 ] . node . peer_disconnected ( node_id_0) ;
2230+
2231+ // Confirm the splice on both sides while disconnected. Each side's `transactions_confirmed`
2232+ // runs `check_get_splice_locked`, which sets `pending_splice.sent_funding_txid` so that
2233+ // `my_current_funding_locked` will carry the splice txid on reconnect. No `splice_locked`
2234+ // messages are queued while disconnected.
2235+ confirm_transaction ( & nodes[ 0 ] , & splice_tx) ;
2236+ confirm_transaction ( & nodes[ 1 ] , & splice_tx) ;
2237+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2238+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2239+
2240+ // Reconnect manually so we can inspect each side's emitted `SendAnnouncementSignatures`.
2241+ // Each side's `channel_reestablish` carries `my_current_funding_locked` with the splice
2242+ // txid, triggering inferred `splice_locked` on the peer. With the fix in place,
2243+ // `announcement_signatures` are generated from the post-splice funding (via the promotion
2244+ // path) rather than the pre-splice funding (via the reestablish handler).
2245+ connect_nodes ( & nodes[ 0 ] , & nodes[ 1 ] ) ;
2246+ let reestablish_0 = get_chan_reestablish_msgs ! ( nodes[ 0 ] , nodes[ 1 ] ) ;
2247+ let reestablish_1 = get_chan_reestablish_msgs ! ( nodes[ 1 ] , nodes[ 0 ] ) ;
2248+ for msg in & reestablish_0 {
2249+ nodes[ 1 ] . node . handle_channel_reestablish ( node_id_0, msg) ;
2250+ }
2251+ for msg in & reestablish_1 {
2252+ nodes[ 0 ] . node . handle_channel_reestablish ( node_id_1, msg) ;
2253+ }
2254+ check_added_monitors ( & nodes[ 0 ] , 1 ) ;
2255+ check_added_monitors ( & nodes[ 1 ] , 1 ) ;
2256+ expect_channel_ready_event ( & nodes[ 0 ] , & node_id_1) ;
2257+ expect_channel_ready_event ( & nodes[ 1 ] , & node_id_0) ;
2258+
2259+ // Each side should emit exactly one `SendAnnouncementSignatures` (post-promotion). The
2260+ // pre-fix behavior would emit a second, stale pre-splice one — our assertion is that the
2261+ // only sigs we send carry the post-splice scid.
2262+ let take_announcement_sigs = |events : Vec < MessageSendEvent > | -> msgs:: AnnouncementSignatures {
2263+ let mut sigs = events. into_iter ( ) . filter_map ( |e| match e {
2264+ MessageSendEvent :: SendAnnouncementSignatures { msg, .. } => Some ( msg) ,
2265+ _ => None ,
2266+ } ) ;
2267+ let only = sigs. next ( ) . expect ( "expected one SendAnnouncementSignatures" ) ;
2268+ assert ! ( sigs. next( ) . is_none( ) , "expected only one SendAnnouncementSignatures" ) ;
2269+ only
2270+ } ;
2271+ let node_0_events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2272+ let node_1_events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
2273+ let node_0_sigs = take_announcement_sigs ( node_0_events) ;
2274+ let node_1_sigs = take_announcement_sigs ( node_1_events) ;
2275+ assert_ne ! ( node_0_sigs. short_channel_id, pre_splice_scid) ;
2276+ assert_ne ! ( node_1_sigs. short_channel_id, pre_splice_scid) ;
2277+
2278+ // Cross-deliver to complete the post-splice announcement exchange, then drain the
2279+ // resulting `BroadcastChannelAnnouncement` events on each side.
2280+ nodes[ 1 ] . node . handle_announcement_signatures ( node_id_0, & node_0_sigs) ;
2281+ nodes[ 0 ] . node . handle_announcement_signatures ( node_id_1, & node_1_sigs) ;
2282+ let _ = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2283+ let _ = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
2284+
2285+ // Channel must still be operational after reconnect — no force-close from mismatched
2286+ // announcement signatures.
2287+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , 1_000_000 ) ;
2288+
2289+ // No stray events or messages left over.
2290+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2291+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2292+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2293+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2294+
2295+ // Clean up chain-source state for the retired pre-splice funding so end-of-test checks pass.
2296+ nodes[ 0 ]
2297+ . chain_source
2298+ . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script. clone ( ) ) ;
2299+ nodes[ 1 ]
2300+ . chain_source
2301+ . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script) ;
2302+ }
2303+
21862304#[ test]
21872305fn test_propose_splice_while_disconnected ( ) {
21882306 do_test_propose_splice_while_disconnected ( false ) ;
0 commit comments