From 3029bf4c9b9bd2e1e6135333679e0fbf66bb9096 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 14 Apr 2026 08:32:33 +0200 Subject: [PATCH 1/2] fuzz: handle BroadcastChannelUpdate in chanmon The regression was introduced in d627ce14c. That change switched fee update opcodes in chanmon_consistency from maybe_update_chan_fees() to timer_tick_occurred(), which can enqueue BroadcastChannelUpdate events while peers are disconnected. The harness already tolerated those events in one delivery path, but still treated them as unreachable in push_excess_b_events and disconnect draining. Accept BroadcastChannelUpdate in those match arms so the fuzz target no longer panics on valid timer tick driven message queues. AI tools were used in preparing this commit. --- fuzz/src/chanmon_consistency.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f20f93c789c..d8d45706a78 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1608,6 +1608,7 @@ pub fn do_test(data: &[u8], out: Out) { }, MessageSendEvent::SendChannelReady { .. } => continue, MessageSendEvent::SendAnnouncementSignatures { .. } => continue, + MessageSendEvent::BroadcastChannelUpdate { .. } => continue, MessageSendEvent::SendChannelUpdate { ref node_id, .. } => { if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } *node_id == a_id @@ -1894,6 +1895,7 @@ pub fn do_test(data: &[u8], out: Out) { MessageSendEvent::SendStfu { .. } => {}, MessageSendEvent::SendChannelReady { .. } => {}, MessageSendEvent::SendAnnouncementSignatures { .. } => {}, + MessageSendEvent::BroadcastChannelUpdate { .. } => {}, MessageSendEvent::SendChannelUpdate { .. } => {}, MessageSendEvent::HandleError { ref action, .. } => { assert_action_timeout_awaiting_response(action); @@ -1916,6 +1918,7 @@ pub fn do_test(data: &[u8], out: Out) { MessageSendEvent::SendStfu { .. } => {}, MessageSendEvent::SendChannelReady { .. } => {}, MessageSendEvent::SendAnnouncementSignatures { .. } => {}, + MessageSendEvent::BroadcastChannelUpdate { .. } => {}, MessageSendEvent::SendChannelUpdate { .. } => {}, MessageSendEvent::HandleError { ref action, .. } => { assert_action_timeout_awaiting_response(action); From 9f59b33e9b3505c1c4dcf36daec8d7a2632accfb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 14 Apr 2026 08:51:15 +0200 Subject: [PATCH 2/2] fuzz: gate splice opcodes on cfg(splicing) Keep the splice opcodes in chanmon_consistency available only when the crate is built with cfg(splicing). When splicing is disabled, return early from those opcode handlers instead of calling splice helpers that are not compiled in. Add cfg(splicing) to fuzz/Cargo.toml check-cfg so the guarded code builds cleanly in the fuzz crate. AI tools were used in preparing this commit. --- fuzz/Cargo.toml | 1 + fuzz/src/chanmon_consistency.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5a2e397a064..252946be458 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -62,4 +62,5 @@ check-cfg = [ "cfg(fuzzing)", "cfg(secp256k1_fuzz)", "cfg(hashes_fuzz)", + "cfg(splicing)", ] diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index d8d45706a78..d4a0e560887 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1333,7 +1333,9 @@ pub fn do_test(data: &[u8], out: Out) { let (node_c, mut monitor_c, keys_manager_c, logger_c) = make_node!(2, fee_est_c, broadcast_c); let mut nodes = [node_a, node_b, node_c]; + #[allow(unused_variables)] let loggers = [logger_a, logger_b, logger_c]; + #[allow(unused_variables)] let fee_estimators = [Arc::clone(&fee_est_a), Arc::clone(&fee_est_b), Arc::clone(&fee_est_c)]; // Connect peers first, then create channels @@ -2426,24 +2428,36 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xa0 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[1].get_our_node_id(); let wallet = WalletSync::new(&wallets[0], Arc::clone(&loggers[0])); let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw(); splice_in(&nodes[0], &cp_node_id, &chan_a_id, &wallet, feerate_sat_per_kw); }, 0xa1 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[0].get_our_node_id(); let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1])); let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); splice_in(&nodes[1], &cp_node_id, &chan_a_id, &wallet, feerate_sat_per_kw); }, 0xa2 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[2].get_our_node_id(); let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1])); let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); splice_in(&nodes[1], &cp_node_id, &chan_b_id, &wallet, feerate_sat_per_kw); }, 0xa3 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[1].get_our_node_id(); let wallet = WalletSync::new(&wallets[2], Arc::clone(&loggers[2])); let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw(); @@ -2451,6 +2465,9 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xa4 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[1].get_our_node_id(); let wallet = &wallets[0]; let logger = Arc::clone(&loggers[0]); @@ -2458,6 +2475,9 @@ pub fn do_test(data: &[u8], out: Out) { splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw); }, 0xa5 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[0].get_our_node_id(); let wallet = &wallets[1]; let logger = Arc::clone(&loggers[1]); @@ -2465,6 +2485,9 @@ pub fn do_test(data: &[u8], out: Out) { splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw); }, 0xa6 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[2].get_our_node_id(); let wallet = &wallets[1]; let logger = Arc::clone(&loggers[1]); @@ -2472,6 +2495,9 @@ pub fn do_test(data: &[u8], out: Out) { splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, logger, feerate_sat_per_kw); }, 0xa7 => { + if !cfg!(splicing) { + test_return!(); + } let cp_node_id = nodes[1].get_our_node_id(); let wallet = &wallets[2]; let logger = Arc::clone(&loggers[2]);