Skip to content

Commit 97e65bc

Browse files
joostjagerclaude
andcommitted
fuzz: add force-close support to chanmon_consistency
Add two new fuzzer actions (0xc0, 0xc1) to force-close one channel on each peer pair (A-B and B-C). This tests channel monitor consistency when channels are intentionally closed. Changes: - Add fc_ab/fc_bc bools to track force-closed channels - Handle HandleError, BroadcastChannelUpdate, and ChannelClosed events generated by force-close - Update test_return! macro to account for closed channels - Skip force-closed channels in 0xff validation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c2eb68f commit 97e65bc

1 file changed

Lines changed: 61 additions & 9 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,16 +1268,29 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
12681268

12691269
let pending_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]);
12701270
let resolved_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]);
1271+
let mut fc_ab = false; // Force-closed one A-B channel
1272+
let mut fc_bc = false; // Force-closed one B-C channel
12711273

12721274
macro_rules! test_return {
12731275
() => {{
1274-
assert_eq!(nodes[0].list_channels().len(), 3);
1275-
assert_eq!(nodes[1].list_channels().len(), 6);
1276-
assert_eq!(nodes[2].list_channels().len(), 3);
1277-
1278-
// At no point should we have broadcasted any transactions after the initial channel
1279-
// opens.
1280-
assert!(broadcast.txn_broadcasted.borrow().is_empty());
1276+
// Node A has 3 A-B channels, minus 1 if we force-closed.
1277+
let expected_a = if fc_ab { 2 } else { 3 };
1278+
assert_eq!(nodes[0].list_channels().len(), expected_a);
1279+
// Node B has 3 A-B + 3 B-C channels. Counterparty may not have processed
1280+
// the force-close yet, so we bound the count.
1281+
let node_b_chans = nodes[1].list_channels().len();
1282+
let min_b = 6 - (if fc_ab { 1 } else { 0 }) - (if fc_bc { 1 } else { 0 });
1283+
let max_b = 6 - (if fc_bc { 1 } else { 0 });
1284+
assert!(node_b_chans >= min_b && node_b_chans <= max_b);
1285+
// Node C has 3 B-C channels. Counterparty may not have processed force-close.
1286+
let node_c_chans = nodes[2].list_channels().len();
1287+
let min_c = if fc_bc { 2 } else { 3 };
1288+
assert!(node_c_chans >= min_c && node_c_chans <= 3);
1289+
1290+
// Only check for no broadcasts if no force-closes happened.
1291+
if !fc_ab && !fc_bc {
1292+
assert!(broadcast.txn_broadcasted.borrow().is_empty());
1293+
}
12811294

12821295
return;
12831296
}};
@@ -1350,6 +1363,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
13501363
},
13511364
MessageSendEvent::SendChannelReady { .. } => continue,
13521365
MessageSendEvent::SendAnnouncementSignatures { .. } => continue,
1366+
MessageSendEvent::HandleError { .. } => continue,
1367+
MessageSendEvent::BroadcastChannelUpdate { .. } => continue,
13531368
MessageSendEvent::SendChannelUpdate { ref node_id, ref msg } => {
13541369
assert_eq!(msg.contents.channel_flags & 2, 0); // The disable bit must never be set!
13551370
if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); }
@@ -1571,6 +1586,12 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
15711586
// force-close which we should detect as an error).
15721587
assert_eq!(msg.contents.channel_flags & 2, 0);
15731588
},
1589+
MessageSendEvent::HandleError { .. } => {
1590+
// Can be generated by force-close
1591+
},
1592+
MessageSendEvent::BroadcastChannelUpdate { .. } => {
1593+
// Can be generated by force-close (with disable bit set)
1594+
},
15741595
_ => if out.may_fail.load(atomic::Ordering::Acquire) {
15751596
return;
15761597
} else {
@@ -1614,6 +1635,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
16141635
MessageSendEvent::SendChannelUpdate { ref msg, .. } => {
16151636
assert_eq!(msg.contents.channel_flags & 2, 0); // The disable bit must never be set!
16161637
},
1638+
MessageSendEvent::HandleError { .. } => {},
1639+
MessageSendEvent::BroadcastChannelUpdate { .. } => {},
16171640
_ => {
16181641
if out.may_fail.load(atomic::Ordering::Acquire) {
16191642
return;
@@ -1641,6 +1664,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
16411664
MessageSendEvent::SendChannelUpdate { ref msg, .. } => {
16421665
assert_eq!(msg.contents.channel_flags & 2, 0); // The disable bit must never be set!
16431666
},
1667+
MessageSendEvent::HandleError { .. } => {},
1668+
MessageSendEvent::BroadcastChannelUpdate { .. } => {},
16441669
_ => {
16451670
if out.may_fail.load(atomic::Ordering::Acquire) {
16461671
return;
@@ -1735,6 +1760,7 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
17351760
},
17361761
events::Event::SplicePending { .. } => {},
17371762
events::Event::SpliceFailed { .. } => {},
1763+
events::Event::ChannelClosed { .. } => {},
17381764

17391765
_ => {
17401766
if out.may_fail.load(atomic::Ordering::Acquire) {
@@ -2326,6 +2352,25 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
23262352
}
23272353
},
23282354

2355+
// Force close A-B channel 0
2356+
0xc0 => {
2357+
let _ = nodes[0].force_close_broadcasting_latest_txn(
2358+
&chan_a_id,
2359+
&nodes[1].get_our_node_id(),
2360+
"]]]]".to_string(),
2361+
);
2362+
fc_ab = true;
2363+
},
2364+
// Force close B-C channel 0
2365+
0xc1 => {
2366+
let _ = nodes[1].force_close_broadcasting_latest_txn(
2367+
&chan_b_id,
2368+
&nodes[2].get_our_node_id(),
2369+
"]]]]".to_string(),
2370+
);
2371+
fc_bc = true;
2372+
},
2373+
23292374
0xb0 | 0xb1 | 0xb2 => {
23302375
// Restart node A, picking among the in-flight `ChannelMonitor`s to use based on
23312376
// the value of `v` we're matching.
@@ -2555,13 +2600,20 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
25552600
}
25562601

25572602
// Finally, make sure that at least one end of each channel can make a substantial payment
2558-
for &scid in &chan_ab_scids {
2603+
// (skip channel 0 if it was force-closed)
2604+
for (i, &scid) in chan_ab_scids.iter().enumerate() {
2605+
if fc_ab && i == 0 {
2606+
continue;
2607+
}
25592608
assert!(
25602609
send(0, 1, scid, 10_000_000, &mut p_ctr)
25612610
|| send(1, 0, scid, 10_000_000, &mut p_ctr)
25622611
);
25632612
}
2564-
for &scid in &chan_bc_scids {
2613+
for (i, &scid) in chan_bc_scids.iter().enumerate() {
2614+
if fc_bc && i == 0 {
2615+
continue;
2616+
}
25652617
assert!(
25662618
send(1, 2, scid, 10_000_000, &mut p_ctr)
25672619
|| send(2, 1, scid, 10_000_000, &mut p_ctr)

0 commit comments

Comments
 (0)