@@ -2199,6 +2199,209 @@ fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
21992199 . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script) ;
22002200}
22012201
2202+ #[ test]
2203+ fn test_splice_confirms_on_both_sides_while_disconnected ( ) {
2204+ // Regression test: when a splice transaction confirms on both sides while peers are
2205+ // disconnected, each peer's `channel_reestablish` carries `my_current_funding_locked` with the
2206+ // splice txid. The receiving side must not emit `announcement_signatures` for the pre-splice
2207+ // funding in that handler — those would be verified against the post-splice channel
2208+ // announcement on the peer and force-close the channel. Instead, sigs are generated after the
2209+ // inferred `splice_locked` promotes the splice funding.
2210+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
2211+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
2212+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
2213+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
2214+
2215+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
2216+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
2217+
2218+ let initial_channel_value_sat = 100_000 ;
2219+ let ( _, _, channel_id, _) =
2220+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
2221+
2222+ let prev_funding_outpoint = get_monitor ! ( nodes[ 0 ] , channel_id) . get_funding_txo ( ) ;
2223+ let prev_funding_script = get_monitor ! ( nodes[ 0 ] , channel_id) . get_funding_script ( ) ;
2224+
2225+ // Capture the pre-splice scid so we can later assert the announcement_sigs each side emits
2226+ // on reconnect carry the post-splice scid, not the pre-splice one the bug would emit.
2227+ let pre_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2228+
2229+ let outputs = vec ! [
2230+ TxOut {
2231+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2232+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2233+ } ,
2234+ TxOut {
2235+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2236+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2237+ } ,
2238+ ] ;
2239+ let funding_contribution =
2240+ initiate_splice_out ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, outputs) . unwrap ( ) ;
2241+ let ( splice_tx, _) = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
2242+
2243+ // Disconnect before either side confirms the splice.
2244+ nodes[ 0 ] . node . peer_disconnected ( node_id_1) ;
2245+ nodes[ 1 ] . node . peer_disconnected ( node_id_0) ;
2246+
2247+ // Confirm the splice on both sides while disconnected. Each side's `transactions_confirmed`
2248+ // runs `check_get_splice_locked`, which sets `pending_splice.sent_funding_txid` so that
2249+ // `my_current_funding_locked` will carry the splice txid on reconnect. No `splice_locked`
2250+ // messages are queued while disconnected.
2251+ confirm_transaction ( & nodes[ 0 ] , & splice_tx) ;
2252+ confirm_transaction ( & nodes[ 1 ] , & splice_tx) ;
2253+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2254+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2255+
2256+ // Reconnect manually so we can inspect each side's emitted `SendAnnouncementSignatures`.
2257+ // Each side's `channel_reestablish` carries `my_current_funding_locked` with the splice
2258+ // txid, triggering inferred `splice_locked` on the peer. With the fix in place,
2259+ // `announcement_signatures` are generated from the post-splice funding (via the promotion
2260+ // path) rather than the pre-splice funding (via the reestablish handler).
2261+ connect_nodes ( & nodes[ 0 ] , & nodes[ 1 ] ) ;
2262+ let reestablish_0 = get_chan_reestablish_msgs ! ( nodes[ 0 ] , nodes[ 1 ] ) ;
2263+ let reestablish_1 = get_chan_reestablish_msgs ! ( nodes[ 1 ] , nodes[ 0 ] ) ;
2264+ for msg in & reestablish_0 {
2265+ nodes[ 1 ] . node . handle_channel_reestablish ( node_id_0, msg) ;
2266+ }
2267+ for msg in & reestablish_1 {
2268+ nodes[ 0 ] . node . handle_channel_reestablish ( node_id_1, msg) ;
2269+ }
2270+ check_added_monitors ( & nodes[ 0 ] , 1 ) ;
2271+ check_added_monitors ( & nodes[ 1 ] , 1 ) ;
2272+ expect_channel_ready_event ( & nodes[ 0 ] , & node_id_1) ;
2273+ expect_channel_ready_event ( & nodes[ 1 ] , & node_id_0) ;
2274+
2275+ // Each side should emit exactly one `SendAnnouncementSignatures` (post-promotion). The
2276+ // pre-fix behavior would emit a second, stale pre-splice one — our assertion is that the
2277+ // only sigs we send carry the post-splice scid.
2278+ let take_announcement_sigs = |events : Vec < MessageSendEvent > | -> msgs:: AnnouncementSignatures {
2279+ let mut sigs = events. into_iter ( ) . filter_map ( |e| match e {
2280+ MessageSendEvent :: SendAnnouncementSignatures { msg, .. } => Some ( msg) ,
2281+ _ => None ,
2282+ } ) ;
2283+ let only = sigs. next ( ) . expect ( "expected one SendAnnouncementSignatures" ) ;
2284+ assert ! ( sigs. next( ) . is_none( ) , "expected only one SendAnnouncementSignatures" ) ;
2285+ only
2286+ } ;
2287+ let node_0_events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2288+ let node_1_events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
2289+ let node_0_sigs = take_announcement_sigs ( node_0_events) ;
2290+ let node_1_sigs = take_announcement_sigs ( node_1_events) ;
2291+ assert_ne ! ( node_0_sigs. short_channel_id, pre_splice_scid) ;
2292+ assert_ne ! ( node_1_sigs. short_channel_id, pre_splice_scid) ;
2293+
2294+ // Cross-deliver to complete the post-splice announcement exchange, then drain the
2295+ // resulting `BroadcastChannelAnnouncement` events on each side.
2296+ nodes[ 1 ] . node . handle_announcement_signatures ( node_id_0, & node_0_sigs) ;
2297+ nodes[ 0 ] . node . handle_announcement_signatures ( node_id_1, & node_1_sigs) ;
2298+ let _ = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2299+ let _ = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
2300+
2301+ // Channel must still be operational after reconnect — no force-close from mismatched
2302+ // announcement signatures.
2303+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , 1_000_000 ) ;
2304+
2305+ // No stray events or messages left over.
2306+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2307+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2308+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2309+ assert ! ( nodes[ 1 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2310+
2311+ // Clean up chain-source state for the retired pre-splice funding so end-of-test checks pass.
2312+ nodes[ 0 ]
2313+ . chain_source
2314+ . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script. clone ( ) ) ;
2315+ nodes[ 1 ]
2316+ . chain_source
2317+ . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script) ;
2318+ }
2319+
2320+ #[ test]
2321+ fn test_stale_announcement_signatures_ignored_after_splice_lock ( ) {
2322+ // Regression test: a peer may transmit `announcement_signatures` signed over a pre-splice
2323+ // `short_channel_id` (for example, a stale retransmission or a peer implementation that
2324+ // hasn't yet caught up to our post-splice promotion). Verifying those sigs against the
2325+ // post-splice `UnsignedChannelAnnouncement` will always fail the hash check, but that is not
2326+ // a protocol violation — the spec permits ignoring and the channel should stay open.
2327+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
2328+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
2329+ let mut config = test_default_channel_config ( ) ;
2330+ config. channel_handshake_config . announced_channel_max_inbound_htlc_value_in_flight_percentage =
2331+ 100 ;
2332+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , Some ( config) ] ) ;
2333+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
2334+
2335+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
2336+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
2337+
2338+ let initial_channel_value_sat = 100_000 ;
2339+ // Use the lower-level helper so we get the signed `ChannelAnnouncement` back — the test
2340+ // needs node 1's pre-splice announcement signatures to replay later.
2341+ let chan_announcement =
2342+ create_chan_between_nodes_with_value ( & nodes[ 0 ] , & nodes[ 1 ] , initial_channel_value_sat, 0 ) ;
2343+ let channel_id = chan_announcement. 3 ;
2344+ update_nodes_with_chan_announce (
2345+ & nodes,
2346+ 0 ,
2347+ 1 ,
2348+ & chan_announcement. 0 ,
2349+ & chan_announcement. 1 ,
2350+ & chan_announcement. 2 ,
2351+ ) ;
2352+
2353+ // Extract node 1's pre-splice signatures from the ChannelAnnouncement. `UnsignedChannelAnnouncement`
2354+ // orders `node_id_1`/`node_id_2` by serialized pubkey; node 1's sigs are in slot 1 iff node 1's
2355+ // pubkey is lexicographically smaller.
2356+ let node_1_is_node_one = node_id_1. serialize ( ) < node_id_0. serialize ( ) ;
2357+ let ( stale_node_sig, stale_bitcoin_sig) = if node_1_is_node_one {
2358+ ( chan_announcement. 0 . node_signature_1 , chan_announcement. 0 . bitcoin_signature_1 )
2359+ } else {
2360+ ( chan_announcement. 0 . node_signature_2 , chan_announcement. 0 . bitcoin_signature_2 )
2361+ } ;
2362+
2363+ // Capture the pre-splice `short_channel_id` — this is the scid the stale sigs sign over.
2364+ let pre_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2365+
2366+ let outputs = vec ! [
2367+ TxOut {
2368+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2369+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2370+ } ,
2371+ TxOut {
2372+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2373+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2374+ } ,
2375+ ] ;
2376+ let funding_contribution =
2377+ initiate_splice_out ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, outputs) . unwrap ( ) ;
2378+ let ( splice_tx, _) = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
2379+ mine_transaction ( & nodes[ 0 ] , & splice_tx) ;
2380+ mine_transaction ( & nodes[ 1 ] , & splice_tx) ;
2381+ lock_splice_after_blocks ( & nodes[ 0 ] , & nodes[ 1 ] , ANTI_REORG_DELAY - 1 ) ;
2382+
2383+ // The post-splice scid is now different; confirm that.
2384+ let post_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2385+ assert_ne ! ( pre_splice_scid, post_splice_scid) ;
2386+
2387+ // Replay node 1's pre-splice announcement signatures, now stale (the current scid is the
2388+ // post-splice one). This is the exact shape of message a peer would send if it retransmitted
2389+ // an old `announcement_signatures` across a splice handoff.
2390+ let stale_sigs = msgs:: AnnouncementSignatures {
2391+ channel_id,
2392+ short_channel_id : pre_splice_scid,
2393+ node_signature : stale_node_sig,
2394+ bitcoin_signature : stale_bitcoin_sig,
2395+ } ;
2396+ nodes[ 0 ] . node . handle_announcement_signatures ( node_id_1, & stale_sigs) ;
2397+
2398+ // No force-close, no outbound error, no events. The channel must still be listed and usable.
2399+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2400+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2401+ assert_eq ! ( nodes[ 0 ] . node. list_channels( ) . len( ) , 1 ) ;
2402+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , 1_000_000 ) ;
2403+ }
2404+
22022405#[ test]
22032406fn test_propose_splice_while_disconnected ( ) {
22042407 do_test_propose_splice_while_disconnected ( false ) ;
0 commit comments