fuzz: expand chanmon_consistency to 6 channels (3 per peer pair)
What changed, and why it matters
This commit only expands an internal fuzz test (a randomized testing harness) from 2 channels to 6 channels. It does not change production code, network behavior, or fix any security issue. The changes are purely about test infrastructure: reusing a peer-connection macro, creating arrays of channel IDs, and using different Bitcoin transaction version numbers so the fuzzer's simplified hash function does not produce accidental collisions. There is no vulnerability here.
No action required. This is a test-only change and does not affect deployed rust-lightning nodes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies fuzz/src/chanmon_consistency.rs, a channel-monitor consistency fuzz target. It refactors make_channel! to extract connect_peers!, then creates three A-B and three B-C channels. It assigns funding transaction versions 1-6 to avoid txid collisions caused by the fuzz harness’s XOR-to-one-byte hash. It updates assertions and monitor-update loops to operate over arrays of channel IDs. No library code, consensus logic, or cryptographic handling is changed.
Changed components
fuzz/src/chanmon_consistency.rsInspect captured patch +174 / −62
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index 202488d..eac993f 100644
--- a/fuzz/src/chanmon_consistency.rs
+++ b/fuzz/src/chanmon_consistency.rs
@@ -848,8 +848,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
}
}};
}
- macro_rules! make_channel {
- ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr) => {{
+ macro_rules! connect_peers {
+ ($source: expr, $dest: expr) => {{
let init_dest = Init {
features: $dest.init_features(),
networks: None,
@@ -862,7 +862,10 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
remote_network_address: None,
};
$dest.peer_connected($source.get_our_node_id(), &init_src, false).unwrap();
-
+ }};
+ }
+ macro_rules! make_channel {
+ ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr) => {{
$source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap();
let open_channel = {
let events = $source.get_and_clear_pending_msg_events();
@@ -1080,8 +1083,26 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
let mut nodes = [node_a, node_b, node_c];
- let chan_1_id = make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 0);
- let chan_2_id = make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 1);
+ // Connect peers first, then create channels
+ connect_peers!(nodes[0], nodes[1]);
+ connect_peers!(nodes[1], nodes[2]);
+
+ // Create 3 channels between A-B and 3 channels between B-C (6 total).
+ //
+ // Use version numbers 1-6 to avoid txid collisions under fuzz hashing.
+ // Fuzz mode uses XOR-based hashing (all bytes XOR to one byte), and
+ // versions 0-5 cause collisions between A-B and B-C channel pairs
+ // (e.g., A-B with Version(1) collides with B-C with Version(3)).
+ let chan_ab_ids = [
+ make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1),
+ make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 2),
+ make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 3),
+ ];
+ let chan_bc_ids = [
+ make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 4),
+ make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 5),
+ make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 6),
+ ];
// Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions
// during normal operation in `test_return`.
@@ -1093,15 +1114,34 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
lock_fundings!(nodes);
- let chan_a = nodes[0].list_usable_channels()[0].short_channel_id.unwrap();
- let chan_a_id = nodes[0].list_usable_channels()[0].channel_id;
- let chan_b = nodes[2].list_usable_channels()[0].short_channel_id.unwrap();
- let chan_b_id = nodes[2].list_usable_channels()[0].channel_id;
+ // Get SCIDs for all A-B channels (from node A's perspective)
+ let node_a_chans: Vec<_> = nodes[0].list_usable_channels();
+ let chan_ab_scids: [u64; 3] = [
+ node_a_chans[0].short_channel_id.unwrap(),
+ node_a_chans[1].short_channel_id.unwrap(),
+ node_a_chans[2].short_channel_id.unwrap(),
+ ];
+ let chan_ab_chan_ids: [ChannelId; 3] =
+ [node_a_chans[0].channel_id, node_a_chans[1].channel_id, node_a_chans[2].channel_id];
+ // Get SCIDs for all B-C channels (from node C's perspective)
+ let node_c_chans: Vec<_> = nodes[2].list_usable_channels();
+ let chan_bc_scids: [u64; 3] = [
+ node_c_chans[0].short_channel_id.unwrap(),
+ node_c_chans[1].short_channel_id.unwrap(),
+ node_c_chans[2].short_channel_id.unwrap(),
+ ];
+ let chan_bc_chan_ids: [ChannelId; 3] =
+ [node_c_chans[0].channel_id, node_c_chans[1].channel_id, node_c_chans[2].channel_id];
+ // Keep old names for backward compatibility in existing code
+ let chan_a = chan_ab_scids[0];
+ let chan_a_id = chan_ab_chan_ids[0];
+ let chan_b = chan_bc_scids[0];
+ let chan_b_id = chan_bc_chan_ids[0];
let mut p_ctr: u64 = 0;
- let mut chan_a_disconnected = false;
- let mut chan_b_disconnected = false;
+ let mut peers_ab_disconnected = false;
+ let mut peers_bc_disconnected = false;
let mut ab_events = Vec::new();
let mut ba_events = Vec::new();
let mut bc_events = Vec::new();
@@ -1116,9 +1156,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
macro_rules! test_return {
() => {{
- assert_eq!(nodes[0].list_channels().len(), 1);
- assert_eq!(nodes[1].list_channels().len(), 2);
- assert_eq!(nodes[2].list_channels().len(), 1);
+ assert_eq!(nodes[0].list_channels().len(), 3);
+ assert_eq!(nodes[1].list_channels().len(), 6);
+ assert_eq!(nodes[2].list_channels().len(), 3);
// At no point should we have broadcasted any transactions after the initial channel
// opens.
@@ -1711,29 +1751,45 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
*mon_style[2].borrow_mut() = ChannelMonitorUpdateStatus::Completed;
},
- 0x08 => complete_all_monitor_updates(&monitor_a, &chan_1_id),
- 0x09 => complete_all_monitor_updates(&monitor_b, &chan_1_id),
- 0x0a => complete_all_monitor_updates(&monitor_b, &chan_2_id),
- 0x0b => complete_all_monitor_updates(&monitor_c, &chan_2_id),
+ 0x08 => {
+ for id in &chan_ab_ids {
+ complete_all_monitor_updates(&monitor_a, id);
+ }
+ },
+ 0x09 => {
+ for id in &chan_ab_ids {
+ complete_all_monitor_updates(&monitor_b, id);
+ }
+ },
+ 0x0a => {
+ for id in &chan_bc_ids {
+ complete_all_monitor_updates(&monitor_b, id);
+ }
+ },
+ 0x0b => {
+ for id in &chan_bc_ids {
+ complete_all_monitor_updates(&monitor_c, id);
+ }
+ },
0x0c => {
- if !chan_a_disconnected {
+ if !peers_ab_disconnected {
nodes[0].peer_disconnected(nodes[1].get_our_node_id());
nodes[1].peer_disconnected(nodes[0].get_our_node_id());
- chan_a_disconnected = true;
+ peers_ab_disconnected = true;
drain_msg_events_on_disconnect!(0);
}
},
0x0d => {
- if !chan_b_disconnected {
+ if !peers_bc_disconnected {
nodes[1].peer_disconnected(nodes[2].get_our_node_id());
nodes[2].peer_disconnected(nodes[1].get_our_node_id());
- chan_b_disconnected = true;
+ peers_bc_disconnected = true;
drain_msg_events_on_disconnect!(2);
}
},
0x0e => {
- if chan_a_disconnected {
+ if peers_ab_disconnected {
let init_1 = Init {
features: nodes[1].init_features(),
networks: None,
@@ -1746,11 +1802,11 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
remote_network_address: None,
};
nodes[1].peer_connected(nodes[0].get_our_node_id(), &init_0, false).unwrap();
- chan_a_disconnected = false;
+ peers_ab_disconnected = false;
}
},
0x0f => {
- if chan_b_disconnected {
+ if peers_bc_disconnected {
let init_2 = Init {
features: nodes[2].init_features(),
networks: None,
@@ -1763,7 +1819,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
remote_network_address: None,
};
nodes[2].peer_connected(nodes[1].get_our_node_id(), &init_1, false).unwrap();
- chan_b_disconnected = false;
+ peers_bc_disconnected = false;
}
},
@@ -2099,9 +2155,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
0xb0 | 0xb1 | 0xb2 => {
// Restart node A, picking among the in-flight `ChannelMonitor`s to use based on
// the value of `v` we're matching.
- if !chan_a_disconnected {
+ if !peers_ab_disconnected {
nodes[1].peer_disconnected(nodes[0].get_our_node_id());
- chan_a_disconnected = true;
+ peers_ab_disconnected = true;
push_excess_b_events!(
nodes[1].get_and_clear_pending_msg_events().drain(..),
Some(0)
@@ -2117,16 +2173,16 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
0xb3..=0xbb => {
// Restart node B, picking among the in-flight `ChannelMonitor`s to use based on
// the value of `v` we're matching.
- if !chan_a_disconnected {
+ if !peers_ab_disconnected {
nodes[0].peer_disconnected(nodes[1].get_our_node_id());
- chan_a_disconnected = true;
+ peers_ab_disconnected = true;
nodes[0].get_and_clear_pending_msg_events();
ab_events.clear();
ba_events.clear();
}
- if !chan_b_disconnected {
+ if !peers_bc_disconnected {
nodes[2].peer_disconnected(nodes[1].get_our_node_id());
- chan_b_disconnected = true;
+ peers_bc_disconnected = true;
nodes[2].get_and_clear_pending_msg_events();
bc_events.clear();
cb_events.clear();
@@ -2139,9 +2195,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
0xbc | 0xbd | 0xbe => {
// Restart node C, picking among the in-flight `ChannelMonitor`s to use based on
// the value of `v` we're matching.
- if !chan_b_disconnected {
+ if !peers_bc_disconnected {
nodes[1].peer_disconnected(nodes[2].get_our_node_id());
- chan_b_disconnected = true;
+ peers_bc_disconnected = true;
push_excess_b_events!(
nodes[1].get_and_clear_pending_msg_events().drain(..),
Some(2)
@@ -2155,28 +2211,76 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
monitor_c = new_monitor_c;
},
- 0xf0 => complete_monitor_update(&monitor_a, &chan_1_id, &complete_first),
- 0xf1 => complete_monitor_update(&monitor_a, &chan_1_id, &complete_second),
- 0xf2 => complete_monitor_update(&monitor_a, &chan_1_id, &Vec::pop),
+ 0xf0 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_a, id, &complete_first);
+ }
+ },
+ 0xf1 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_a, id, &complete_second);
+ }
+ },
+ 0xf2 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_a, id, &Vec::pop);
+ }
+ },
- 0xf4 => complete_monitor_update(&monitor_b, &chan_1_id, &complete_first),
- 0xf5 => complete_monitor_update(&monitor_b, &chan_1_id, &complete_second),
- 0xf6 => complete_monitor_update(&monitor_b, &chan_1_id, &Vec::pop),
+ 0xf4 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_b, id, &complete_first);
+ }
+ },
+ 0xf5 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_b, id, &complete_second);
+ }
+ },
+ 0xf6 => {
+ for id in &chan_ab_ids {
+ complete_monitor_update(&monitor_b, id, &Vec::pop);
+ }
+ },
- 0xf8 => complete_monitor_update(&monitor_b, &chan_2_id, &complete_first),
- 0xf9 => complete_monitor_update(&monitor_b, &chan_2_id, &complete_second),
- 0xfa => complete_monitor_update(&monitor_b, &chan_2_id, &Vec::pop),
+ 0xf8 => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_b, id, &complete_first);
+ }
+ },
+ 0xf9 => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_b, id, &complete_second);
+ }
+ },
+ 0xfa => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_b, id, &Vec::pop);
+ }
+ },
- 0xfc => complete_monitor_update(&monitor_c, &chan_2_id, &complete_first),
- 0xfd => complete_monitor_update(&monitor_c, &chan_2_id, &complete_second),
- 0xfe => complete_monitor_update(&monitor_c, &chan_2_id, &Vec::pop),
+ 0xfc => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_c, id, &complete_first);
+ }
+ },
+ 0xfd => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_c, id, &complete_second);
+ }
+ },
+ 0xfe => {
+ for id in &chan_bc_ids {
+ complete_monitor_update(&monitor_c, id, &Vec::pop);
+ }
+ },
0xff => {
// Test that no channel is in a stuck state where neither party can send funds even
// after we resolve all pending events.
// First, make sure peers are all connected to each other
- if chan_a_disconnected {
+ if peers_ab_disconnected {
let init_1 = Init {
features: nodes[1].init_features(),
networks: None,
@@ -2189,9 +2293,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
remote_network_address: None,
};
nodes[1].peer_connected(nodes[0].get_our_node_id(), &init_0, false).unwrap();
- chan_a_disconnected = false;
+ peers_ab_disconnected = false;
}
- if chan_b_disconnected {
+ if peers_bc_disconnected {
let init_2 = Init {
features: nodes[2].init_features(),
networks: None,
@@ -2204,7 +2308,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
remote_network_address: None,
};
nodes[2].peer_connected(nodes[1].get_our_node_id(), &init_1, false).unwrap();
- chan_b_disconnected = false;
+ peers_bc_disconnected = false;
}
macro_rules! process_all_events {
@@ -2215,10 +2319,14 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
panic!("It may take may iterations to settle the state, but it should not take forever");
}
// Next, make sure no monitor updates are pending
- complete_all_monitor_updates(&monitor_a, &chan_1_id);
- complete_all_monitor_updates(&monitor_b, &chan_1_id);
- complete_all_monitor_updates(&monitor_b, &chan_2_id);
- complete_all_monitor_updates(&monitor_c, &chan_2_id);
+ for id in &chan_ab_ids {
+ complete_all_monitor_updates(&monitor_a, id);
+ complete_all_monitor_updates(&monitor_b, id);
+ }
+ for id in &chan_bc_ids {
+ complete_all_monitor_updates(&monitor_b, id);
+ complete_all_monitor_updates(&monitor_c, id);
+ }
// Then, make sure any current forwards make their way to their destination
if process_msg_events!(0, false, ProcessMessages::AllMessages) {
last_pass_no_updates = false;
@@ -2263,14 +2371,18 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
process_all_events!();
// Finally, make sure that at least one end of each channel can make a substantial payment
- assert!(
- send(0, 1, chan_a, 10_000_000, &mut p_ctr)
- || send(1, 0, chan_a, 10_000_000, &mut p_ctr)
- );
- assert!(
- send(1, 2, chan_b, 10_000_000, &mut p_ctr)
- || send(2, 1, chan_b, 10_000_000, &mut p_ctr)
- );
+ for &scid in &chan_ab_scids {
+ assert!(
+ send(0, 1, scid, 10_000_000, &mut p_ctr)
+ || send(1, 0, scid, 10_000_000, &mut p_ctr)
+ );
+ }
+ for &scid in &chan_bc_scids {
+ assert!(
+ send(1, 2, scid, 10_000_000, &mut p_ctr)
+ || send(2, 1, scid, 10_000_000, &mut p_ctr)
+ );
+ }
last_htlc_clear_fee_a = fee_est_a.ret_val.load(atomic::Ordering::Acquire);
last_htlc_clear_fee_b = fee_est_b.ret_val.load(atomic::Ordering::Acquire);
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.