From 24c61fa562085879b393d548553d5ad176045b61 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 10:26:04 +0100 Subject: [PATCH 1/6] fuzz: consolidate payment counters into single counter Replace the separate `p_id: u8` (for payment hash generation) and `p_idx: u64` (for payment IDs) with a single `p_ctr: u64` counter. Co-Authored-By: Claude Opus 4.5 Co-Authored-By: Matt Corallo --- fuzz/src/chanmon_consistency.rs | 190 ++++++++++++++------------------ 1 file changed, 82 insertions(+), 108 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 03d170b1bc0..e5783b3bfba 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -539,43 +539,40 @@ type ChanMan<'a> = ChannelManager< #[inline] fn get_payment_secret_hash( - dest: &ChanMan, payment_id: &mut u8, + dest: &ChanMan, payment_ctr: &mut u64, ) -> Option<(PaymentSecret, PaymentHash)> { let mut payment_hash; for _ in 0..256 { - payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).to_byte_array()); + *payment_ctr += 1; + payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array()); if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) { return Some((payment_secret, payment_hash)); } - *payment_id = payment_id.wrapping_add(1); } None } #[inline] fn send_noret( - source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_id: &mut u8, - payment_idx: &mut u64, + source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, ) { - send_payment(source, dest, dest_chan_id, amt, payment_id, payment_idx); + send_payment(source, dest, dest_chan_id, amt, payment_ctr); } #[inline] fn send_payment( - source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_id: &mut u8, - payment_idx: &mut u64, + source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, ) -> bool { let (payment_secret, payment_hash) = - if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_id) { + if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_ctr) { (secret, hash) } else { return true; }; let mut payment_id = [0; 32]; - payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes()); - *payment_idx += 1; + payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source .list_usable_channels() .iter() @@ -620,34 +617,24 @@ fn send_payment( #[inline] fn send_hop_noret( source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, - amt: u64, payment_id: &mut u8, payment_idx: &mut u64, + amt: u64, payment_ctr: &mut u64, ) { - send_hop_payment( - source, - middle, - middle_chan_id, - dest, - dest_chan_id, - amt, - payment_id, - payment_idx, - ); + send_hop_payment(source, middle, middle_chan_id, dest, dest_chan_id, amt, payment_ctr); } #[inline] fn send_hop_payment( source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, - amt: u64, payment_id: &mut u8, payment_idx: &mut u64, + amt: u64, payment_ctr: &mut u64, ) -> bool { let (payment_secret, payment_hash) = - if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_id) { + if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_ctr) { (secret, hash) } else { return true; }; let mut payment_id = [0; 32]; - payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes()); - *payment_idx += 1; + payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source .list_usable_channels() .iter() @@ -1138,8 +1125,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { 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; - let mut p_id: u8 = 0; - let mut p_idx: u64 = 0; + let mut p_ctr: u64 = 0; let mut chan_a_disconnected = false; let mut chan_b_disconnected = false; @@ -1762,93 +1748,85 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { 0x27 => process_ev_noret!(2, false), // 1/10th the channel size: - 0x30 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_id, &mut p_idx), - 0x31 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_id, &mut p_idx), - 0x32 => send_noret(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_id, &mut p_idx), - 0x33 => send_noret(&nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_id, &mut p_idx), + 0x30 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_ctr), + 0x31 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_ctr), + 0x32 => send_noret(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_ctr), + 0x33 => send_noret(&nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_ctr), 0x34 => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000_000, &mut p_id, &mut p_idx, + &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000_000, &mut p_ctr, ), 0x35 => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000_000, &mut p_id, &mut p_idx, + &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000_000, &mut p_ctr, ), - 0x38 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000_000, &mut p_id, &mut p_idx), - 0x39 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000_000, &mut p_id, &mut p_idx), - 0x3a => send_noret(&nodes[1], &nodes[2], chan_b, 1_000_000, &mut p_id, &mut p_idx), - 0x3b => send_noret(&nodes[2], &nodes[1], chan_b, 1_000_000, &mut p_id, &mut p_idx), + 0x38 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000_000, &mut p_ctr), + 0x39 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000_000, &mut p_ctr), + 0x3a => send_noret(&nodes[1], &nodes[2], chan_b, 1_000_000, &mut p_ctr), + 0x3b => send_noret(&nodes[2], &nodes[1], chan_b, 1_000_000, &mut p_ctr), 0x3c => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000_000, &mut p_id, &mut p_idx, + &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000_000, &mut p_ctr, ), 0x3d => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000_000, &mut p_id, &mut p_idx, + &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000_000, &mut p_ctr, ), - 0x40 => send_noret(&nodes[0], &nodes[1], chan_a, 100_000, &mut p_id, &mut p_idx), - 0x41 => send_noret(&nodes[1], &nodes[0], chan_a, 100_000, &mut p_id, &mut p_idx), - 0x42 => send_noret(&nodes[1], &nodes[2], chan_b, 100_000, &mut p_id, &mut p_idx), - 0x43 => send_noret(&nodes[2], &nodes[1], chan_b, 100_000, &mut p_id, &mut p_idx), - 0x44 => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100_000, &mut p_id, &mut p_idx, - ), - 0x45 => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100_000, &mut p_id, &mut p_idx, - ), + 0x40 => send_noret(&nodes[0], &nodes[1], chan_a, 100_000, &mut p_ctr), + 0x41 => send_noret(&nodes[1], &nodes[0], chan_a, 100_000, &mut p_ctr), + 0x42 => send_noret(&nodes[1], &nodes[2], chan_b, 100_000, &mut p_ctr), + 0x43 => send_noret(&nodes[2], &nodes[1], chan_b, 100_000, &mut p_ctr), + 0x44 => { + send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100_000, &mut p_ctr) + }, + 0x45 => { + send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100_000, &mut p_ctr) + }, - 0x48 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000, &mut p_id, &mut p_idx), - 0x49 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000, &mut p_id, &mut p_idx), - 0x4a => send_noret(&nodes[1], &nodes[2], chan_b, 10_000, &mut p_id, &mut p_idx), - 0x4b => send_noret(&nodes[2], &nodes[1], chan_b, 10_000, &mut p_id, &mut p_idx), - 0x4c => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000, &mut p_id, &mut p_idx, - ), - 0x4d => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000, &mut p_id, &mut p_idx, - ), + 0x48 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000, &mut p_ctr), + 0x49 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000, &mut p_ctr), + 0x4a => send_noret(&nodes[1], &nodes[2], chan_b, 10_000, &mut p_ctr), + 0x4b => send_noret(&nodes[2], &nodes[1], chan_b, 10_000, &mut p_ctr), + 0x4c => { + send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000, &mut p_ctr) + }, + 0x4d => { + send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000, &mut p_ctr) + }, - 0x50 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000, &mut p_id, &mut p_idx), - 0x51 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000, &mut p_id, &mut p_idx), - 0x52 => send_noret(&nodes[1], &nodes[2], chan_b, 1_000, &mut p_id, &mut p_idx), - 0x53 => send_noret(&nodes[2], &nodes[1], chan_b, 1_000, &mut p_id, &mut p_idx), - 0x54 => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000, &mut p_id, &mut p_idx, - ), - 0x55 => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000, &mut p_id, &mut p_idx, - ), + 0x50 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000, &mut p_ctr), + 0x51 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000, &mut p_ctr), + 0x52 => send_noret(&nodes[1], &nodes[2], chan_b, 1_000, &mut p_ctr), + 0x53 => send_noret(&nodes[2], &nodes[1], chan_b, 1_000, &mut p_ctr), + 0x54 => { + send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000, &mut p_ctr) + }, + 0x55 => { + send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000, &mut p_ctr) + }, - 0x58 => send_noret(&nodes[0], &nodes[1], chan_a, 100, &mut p_id, &mut p_idx), - 0x59 => send_noret(&nodes[1], &nodes[0], chan_a, 100, &mut p_id, &mut p_idx), - 0x5a => send_noret(&nodes[1], &nodes[2], chan_b, 100, &mut p_id, &mut p_idx), - 0x5b => send_noret(&nodes[2], &nodes[1], chan_b, 100, &mut p_id, &mut p_idx), - 0x5c => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100, &mut p_id, &mut p_idx, - ), - 0x5d => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100, &mut p_id, &mut p_idx, - ), + 0x58 => send_noret(&nodes[0], &nodes[1], chan_a, 100, &mut p_ctr), + 0x59 => send_noret(&nodes[1], &nodes[0], chan_a, 100, &mut p_ctr), + 0x5a => send_noret(&nodes[1], &nodes[2], chan_b, 100, &mut p_ctr), + 0x5b => send_noret(&nodes[2], &nodes[1], chan_b, 100, &mut p_ctr), + 0x5c => { + send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100, &mut p_ctr) + }, + 0x5d => { + send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100, &mut p_ctr) + }, - 0x60 => send_noret(&nodes[0], &nodes[1], chan_a, 10, &mut p_id, &mut p_idx), - 0x61 => send_noret(&nodes[1], &nodes[0], chan_a, 10, &mut p_id, &mut p_idx), - 0x62 => send_noret(&nodes[1], &nodes[2], chan_b, 10, &mut p_id, &mut p_idx), - 0x63 => send_noret(&nodes[2], &nodes[1], chan_b, 10, &mut p_id, &mut p_idx), - 0x64 => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10, &mut p_id, &mut p_idx, - ), - 0x65 => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10, &mut p_id, &mut p_idx, - ), + 0x60 => send_noret(&nodes[0], &nodes[1], chan_a, 10, &mut p_ctr), + 0x61 => send_noret(&nodes[1], &nodes[0], chan_a, 10, &mut p_ctr), + 0x62 => send_noret(&nodes[1], &nodes[2], chan_b, 10, &mut p_ctr), + 0x63 => send_noret(&nodes[2], &nodes[1], chan_b, 10, &mut p_ctr), + 0x64 => send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10, &mut p_ctr), + 0x65 => send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10, &mut p_ctr), - 0x68 => send_noret(&nodes[0], &nodes[1], chan_a, 1, &mut p_id, &mut p_idx), - 0x69 => send_noret(&nodes[1], &nodes[0], chan_a, 1, &mut p_id, &mut p_idx), - 0x6a => send_noret(&nodes[1], &nodes[2], chan_b, 1, &mut p_id, &mut p_idx), - 0x6b => send_noret(&nodes[2], &nodes[1], chan_b, 1, &mut p_id, &mut p_idx), - 0x6c => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1, &mut p_id, &mut p_idx, - ), - 0x6d => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1, &mut p_id, &mut p_idx, - ), + 0x68 => send_noret(&nodes[0], &nodes[1], chan_a, 1, &mut p_ctr), + 0x69 => send_noret(&nodes[1], &nodes[0], chan_a, 1, &mut p_ctr), + 0x6a => send_noret(&nodes[1], &nodes[2], chan_b, 1, &mut p_ctr), + 0x6b => send_noret(&nodes[2], &nodes[1], chan_b, 1, &mut p_ctr), + 0x6c => send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1, &mut p_ctr), + 0x6d => send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1, &mut p_ctr), 0x80 => { let mut max_feerate = last_htlc_clear_fee_a; @@ -2280,16 +2258,12 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { // Finally, make sure that at least one end of each channel can make a substantial payment assert!( - send_payment(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_id, &mut p_idx) - || send_payment( - &nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_id, &mut p_idx - ) + send_payment(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_ctr) + || send_payment(&nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_ctr) ); assert!( - send_payment(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_id, &mut p_idx) - || send_payment( - &nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_id, &mut p_idx - ) + send_payment(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_ctr) + || send_payment(&nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_ctr) ); last_htlc_clear_fee_a = fee_est_a.ret_val.load(atomic::Ordering::Acquire); From 7d51892254d29b6ab0ad1c7dc8b802d95c4f6611 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 10:26:42 +0100 Subject: [PATCH 2/6] fuzz: simplify get_payment_secret_hash return type Change `get_payment_secret_hash` to return `(PaymentSecret, PaymentHash)` directly instead of `Option<...>`. The function calls `create_inbound_payment_for_hash` with `min_value_msat=None` and `min_final_cltv_expiry=None`, which cannot fail. Remove the retry loop and use `.expect()` since the call will always succeed with these parameters. Co-Authored-By: Claude Opus 4.5 Co-Authored-By: Matt Corallo --- fuzz/src/chanmon_consistency.rs | 35 +++++++++------------------------ 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e5783b3bfba..d3d38a99dea 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -538,20 +538,13 @@ type ChanMan<'a> = ChannelManager< >; #[inline] -fn get_payment_secret_hash( - dest: &ChanMan, payment_ctr: &mut u64, -) -> Option<(PaymentSecret, PaymentHash)> { - let mut payment_hash; - for _ in 0..256 { - *payment_ctr += 1; - payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array()); - if let Ok(payment_secret) = - dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) - { - return Some((payment_secret, payment_hash)); - } - } - None +fn get_payment_secret_hash(dest: &ChanMan, payment_ctr: &mut u64) -> (PaymentSecret, PaymentHash) { + *payment_ctr += 1; + let payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array()); + let payment_secret = dest + .create_inbound_payment_for_hash(payment_hash, None, 3600, None) + .expect("create_inbound_payment_for_hash failed"); + (payment_secret, payment_hash) } #[inline] @@ -565,12 +558,7 @@ fn send_noret( fn send_payment( source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, ) -> bool { - let (payment_secret, payment_hash) = - if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_ctr) { - (secret, hash) - } else { - return true; - }; + let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); let mut payment_id = [0; 32]; payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source @@ -627,12 +615,7 @@ fn send_hop_payment( source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, ) -> bool { - let (payment_secret, payment_hash) = - if let Some((secret, hash)) = get_payment_secret_hash(dest, payment_ctr) { - (secret, hash) - } else { - return true; - }; + let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); let mut payment_id = [0; 32]; payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source From 151762c83ece0e3b1906dded00dae48b1db931bb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 10:27:10 +0100 Subject: [PATCH 3/6] fuzz: rename chan_id to scid in hop payment functions Rename `middle_chan_id` to `middle_scid` and `dest_chan_id` to `dest_scid` in `send_hop_noret` and `send_hop_payment`. These parameters are short channel IDs (SCIDs), not channel IDs, so the rename improves clarity. Co-Authored-By: Claude Opus 4.5 Co-Authored-By: Matt Corallo --- fuzz/src/chanmon_consistency.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index d3d38a99dea..de83f8065ea 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -604,15 +604,15 @@ fn send_payment( #[inline] fn send_hop_noret( - source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, + source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, payment_ctr: &mut u64, ) { - send_hop_payment(source, middle, middle_chan_id, dest, dest_chan_id, amt, payment_ctr); + send_hop_payment(source, middle, middle_scid, dest, dest_scid, amt, payment_ctr); } #[inline] fn send_hop_payment( - source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, dest: &ChanMan, dest_chan_id: u64, + source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, payment_ctr: &mut u64, ) -> bool { let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); @@ -621,7 +621,7 @@ fn send_hop_payment( let (min_value_sendable, max_value_sendable) = source .list_usable_channels() .iter() - .find(|chan| chan.short_channel_id == Some(middle_chan_id)) + .find(|chan| chan.short_channel_id == Some(middle_scid)) .map(|chan| (chan.next_outbound_htlc_minimum_msat, chan.next_outbound_htlc_limit_msat)) .unwrap_or((0, 0)); let first_hop_fee = 50_000; @@ -635,7 +635,7 @@ fn send_hop_payment( RouteHop { pubkey: middle.get_our_node_id(), node_features: middle.node_features(), - short_channel_id: middle_chan_id, + short_channel_id: middle_scid, channel_features: middle.channel_features(), fee_msat: first_hop_fee, cltv_expiry_delta: 100, @@ -644,7 +644,7 @@ fn send_hop_payment( RouteHop { pubkey: dest.get_our_node_id(), node_features: dest.node_features(), - short_channel_id: dest_chan_id, + short_channel_id: dest_scid, channel_features: dest.channel_features(), fee_msat: amt, cltv_expiry_delta: 200, From ecf502a768466e2df5ecfde30bbca48819925f4c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 12:44:21 +0100 Subject: [PATCH 4/6] fuzz: add transaction broadcast assertions Add transaction tracking to TestBroadcaster and verify no unexpected broadcasts occur during normal fuzzing operation: - Store all broadcast transactions in TestBroadcaster - Clear the broadcast set after initial channel opens - Assert in test_return! that no transactions were broadcast Co-Authored-By: Claude Opus 4.5 --- fuzz/src/chanmon_consistency.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index de83f8065ea..7cc7986c1cd 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -155,9 +155,15 @@ impl MessageRouter for FuzzRouter { } } -pub struct TestBroadcaster {} +pub struct TestBroadcaster { + txn_broadcasted: RefCell>, +} impl BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, _txs: &[&Transaction]) {} + fn broadcast_transactions(&self, txs: &[&Transaction]) { + for tx in txs { + self.txn_broadcasted.borrow_mut().push((*tx).clone()); + } + } } pub struct VecWriter(pub Vec); @@ -334,7 +340,7 @@ impl chain::Watch for TestChainMonitor { deserialized_monitor .update_monitor( update, - &&TestBroadcaster {}, + &&TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }, &&FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }, &self.logger, ) @@ -604,16 +610,16 @@ fn send_payment( #[inline] fn send_hop_noret( - source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, - amt: u64, payment_ctr: &mut u64, + source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, + payment_ctr: &mut u64, ) { send_hop_payment(source, middle, middle_scid, dest, dest_scid, amt, payment_ctr); } #[inline] fn send_hop_payment( - source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, - amt: u64, payment_ctr: &mut u64, + source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, + payment_ctr: &mut u64, ) -> bool { let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); let mut payment_id = [0; 32]; @@ -675,7 +681,7 @@ fn send_hop_payment( #[inline] pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let out = SearchingOutput::new(underlying_out); - let broadcast = Arc::new(TestBroadcaster {}); + let broadcast = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); let router = FuzzRouter {}; // Read initial monitor styles from fuzz input (1 byte: 2 bits per node) @@ -1097,6 +1103,10 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { 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); + // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions + // during normal operation in `test_return`. + broadcast.txn_broadcasted.borrow_mut().clear(); + for node in nodes.iter() { confirm_txn!(node); } @@ -1126,6 +1136,11 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { assert_eq!(nodes[0].list_channels().len(), 1); assert_eq!(nodes[1].list_channels().len(), 2); assert_eq!(nodes[2].list_channels().len(), 1); + + // At no point should we have broadcasted any transactions after the initial channel + // opens. + assert!(broadcast.txn_broadcasted.borrow().is_empty()); + return; }}; } From 0a48663f10c6f59fa9b3b29cb06db1b14c3ab494 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 10:29:08 +0100 Subject: [PATCH 5/6] fuzz: convert send helpers to closures with node indices Replace the standalone `send_noret` and `send_hop_noret` functions with closures defined inside the main loop. This allows them to capture the `nodes` array and use simple node indices (0, 1, 2) instead of passing `&nodes[x]` references at each call site. The `send_payment` and `send_hop_payment` functions are modified to take pre-computed `PaymentSecret`, `PaymentHash`, and `PaymentId` parameters, with the closures handling the credential generation. This centralizes where `PaymentId` is constructed, which will make it easier to add payment tracking in a future change. Co-Authored-By: Claude Opus 4.5 Co-Authored-By: Matt Corallo --- fuzz/src/chanmon_consistency.rs | 199 +++++++++++++++----------------- 1 file changed, 91 insertions(+), 108 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 7cc7986c1cd..1bcb6697a86 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -553,20 +553,11 @@ fn get_payment_secret_hash(dest: &ChanMan, payment_ctr: &mut u64) -> (PaymentSec (payment_secret, payment_hash) } -#[inline] -fn send_noret( - source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, -) { - send_payment(source, dest, dest_chan_id, amt, payment_ctr); -} - #[inline] fn send_payment( - source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_ctr: &mut u64, + source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, payment_secret: PaymentSecret, + payment_hash: PaymentHash, payment_id: PaymentId, ) -> bool { - let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); - let mut payment_id = [0; 32]; - payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source .list_usable_channels() .iter() @@ -593,7 +584,6 @@ fn send_payment( route_params: Some(route_params.clone()), }; let onion = RecipientOnionFields::secret_only(payment_secret); - let payment_id = PaymentId(payment_id); let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); match res { Err(err) => { @@ -608,22 +598,11 @@ fn send_payment( } } -#[inline] -fn send_hop_noret( - source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, - payment_ctr: &mut u64, -) { - send_hop_payment(source, middle, middle_scid, dest, dest_scid, amt, payment_ctr); -} - #[inline] fn send_hop_payment( source: &ChanMan, middle: &ChanMan, middle_scid: u64, dest: &ChanMan, dest_scid: u64, amt: u64, - payment_ctr: &mut u64, + payment_secret: PaymentSecret, payment_hash: PaymentHash, payment_id: PaymentId, ) -> bool { - let (payment_secret, payment_hash) = get_payment_secret_hash(dest, payment_ctr); - let mut payment_id = [0; 32]; - payment_id[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let (min_value_sendable, max_value_sendable) = source .list_usable_channels() .iter() @@ -662,7 +641,6 @@ fn send_hop_payment( route_params: Some(route_params.clone()), }; let onion = RecipientOnionFields::secret_only(payment_secret); - let payment_id = PaymentId(payment_id); let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); match res { Err(err) => { @@ -1634,6 +1612,35 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { } }; + let send = + |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { + let source = &nodes[source_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let mut id = PaymentId([0; 32]); + id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); + send_payment(source, dest, dest_chan_id, amt, secret, hash, id) + }; + let send_noret = |source_idx, dest_idx, dest_chan_id, amt, payment_ctr: &mut u64| { + send(source_idx, dest_idx, dest_chan_id, amt, payment_ctr); + }; + + let send_hop_noret = |source_idx: usize, + middle_idx: usize, + middle_scid: u64, + dest_idx: usize, + dest_scid: u64, + amt: u64, + payment_ctr: &mut u64| { + let source = &nodes[source_idx]; + let middle = &nodes[middle_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let mut id = PaymentId([0; 32]); + id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); + send_hop_payment(source, middle, middle_scid, dest, dest_scid, amt, secret, hash, id); + }; + let v = get_slice!(1)[0]; out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); match v { @@ -1746,85 +1753,61 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { 0x27 => process_ev_noret!(2, false), // 1/10th the channel size: - 0x30 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_ctr), - 0x31 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_ctr), - 0x32 => send_noret(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_ctr), - 0x33 => send_noret(&nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_ctr), - 0x34 => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000_000, &mut p_ctr, - ), - 0x35 => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000_000, &mut p_ctr, - ), - - 0x38 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000_000, &mut p_ctr), - 0x39 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000_000, &mut p_ctr), - 0x3a => send_noret(&nodes[1], &nodes[2], chan_b, 1_000_000, &mut p_ctr), - 0x3b => send_noret(&nodes[2], &nodes[1], chan_b, 1_000_000, &mut p_ctr), - 0x3c => send_hop_noret( - &nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000_000, &mut p_ctr, - ), - 0x3d => send_hop_noret( - &nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000_000, &mut p_ctr, - ), - - 0x40 => send_noret(&nodes[0], &nodes[1], chan_a, 100_000, &mut p_ctr), - 0x41 => send_noret(&nodes[1], &nodes[0], chan_a, 100_000, &mut p_ctr), - 0x42 => send_noret(&nodes[1], &nodes[2], chan_b, 100_000, &mut p_ctr), - 0x43 => send_noret(&nodes[2], &nodes[1], chan_b, 100_000, &mut p_ctr), - 0x44 => { - send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100_000, &mut p_ctr) - }, - 0x45 => { - send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100_000, &mut p_ctr) - }, - - 0x48 => send_noret(&nodes[0], &nodes[1], chan_a, 10_000, &mut p_ctr), - 0x49 => send_noret(&nodes[1], &nodes[0], chan_a, 10_000, &mut p_ctr), - 0x4a => send_noret(&nodes[1], &nodes[2], chan_b, 10_000, &mut p_ctr), - 0x4b => send_noret(&nodes[2], &nodes[1], chan_b, 10_000, &mut p_ctr), - 0x4c => { - send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10_000, &mut p_ctr) - }, - 0x4d => { - send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10_000, &mut p_ctr) - }, - - 0x50 => send_noret(&nodes[0], &nodes[1], chan_a, 1_000, &mut p_ctr), - 0x51 => send_noret(&nodes[1], &nodes[0], chan_a, 1_000, &mut p_ctr), - 0x52 => send_noret(&nodes[1], &nodes[2], chan_b, 1_000, &mut p_ctr), - 0x53 => send_noret(&nodes[2], &nodes[1], chan_b, 1_000, &mut p_ctr), - 0x54 => { - send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1_000, &mut p_ctr) - }, - 0x55 => { - send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1_000, &mut p_ctr) - }, - - 0x58 => send_noret(&nodes[0], &nodes[1], chan_a, 100, &mut p_ctr), - 0x59 => send_noret(&nodes[1], &nodes[0], chan_a, 100, &mut p_ctr), - 0x5a => send_noret(&nodes[1], &nodes[2], chan_b, 100, &mut p_ctr), - 0x5b => send_noret(&nodes[2], &nodes[1], chan_b, 100, &mut p_ctr), - 0x5c => { - send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 100, &mut p_ctr) - }, - 0x5d => { - send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 100, &mut p_ctr) - }, - - 0x60 => send_noret(&nodes[0], &nodes[1], chan_a, 10, &mut p_ctr), - 0x61 => send_noret(&nodes[1], &nodes[0], chan_a, 10, &mut p_ctr), - 0x62 => send_noret(&nodes[1], &nodes[2], chan_b, 10, &mut p_ctr), - 0x63 => send_noret(&nodes[2], &nodes[1], chan_b, 10, &mut p_ctr), - 0x64 => send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 10, &mut p_ctr), - 0x65 => send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 10, &mut p_ctr), - - 0x68 => send_noret(&nodes[0], &nodes[1], chan_a, 1, &mut p_ctr), - 0x69 => send_noret(&nodes[1], &nodes[0], chan_a, 1, &mut p_ctr), - 0x6a => send_noret(&nodes[1], &nodes[2], chan_b, 1, &mut p_ctr), - 0x6b => send_noret(&nodes[2], &nodes[1], chan_b, 1, &mut p_ctr), - 0x6c => send_hop_noret(&nodes[0], &nodes[1], chan_a, &nodes[2], chan_b, 1, &mut p_ctr), - 0x6d => send_hop_noret(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1, &mut p_ctr), + 0x30 => send_noret(0, 1, chan_a, 10_000_000, &mut p_ctr), + 0x31 => send_noret(1, 0, chan_a, 10_000_000, &mut p_ctr), + 0x32 => send_noret(1, 2, chan_b, 10_000_000, &mut p_ctr), + 0x33 => send_noret(2, 1, chan_b, 10_000_000, &mut p_ctr), + 0x34 => send_hop_noret(0, 1, chan_a, 2, chan_b, 10_000_000, &mut p_ctr), + 0x35 => send_hop_noret(2, 1, chan_b, 0, chan_a, 10_000_000, &mut p_ctr), + + 0x38 => send_noret(0, 1, chan_a, 1_000_000, &mut p_ctr), + 0x39 => send_noret(1, 0, chan_a, 1_000_000, &mut p_ctr), + 0x3a => send_noret(1, 2, chan_b, 1_000_000, &mut p_ctr), + 0x3b => send_noret(2, 1, chan_b, 1_000_000, &mut p_ctr), + 0x3c => send_hop_noret(0, 1, chan_a, 2, chan_b, 1_000_000, &mut p_ctr), + 0x3d => send_hop_noret(2, 1, chan_b, 0, chan_a, 1_000_000, &mut p_ctr), + + 0x40 => send_noret(0, 1, chan_a, 100_000, &mut p_ctr), + 0x41 => send_noret(1, 0, chan_a, 100_000, &mut p_ctr), + 0x42 => send_noret(1, 2, chan_b, 100_000, &mut p_ctr), + 0x43 => send_noret(2, 1, chan_b, 100_000, &mut p_ctr), + 0x44 => send_hop_noret(0, 1, chan_a, 2, chan_b, 100_000, &mut p_ctr), + 0x45 => send_hop_noret(2, 1, chan_b, 0, chan_a, 100_000, &mut p_ctr), + + 0x48 => send_noret(0, 1, chan_a, 10_000, &mut p_ctr), + 0x49 => send_noret(1, 0, chan_a, 10_000, &mut p_ctr), + 0x4a => send_noret(1, 2, chan_b, 10_000, &mut p_ctr), + 0x4b => send_noret(2, 1, chan_b, 10_000, &mut p_ctr), + 0x4c => send_hop_noret(0, 1, chan_a, 2, chan_b, 10_000, &mut p_ctr), + 0x4d => send_hop_noret(2, 1, chan_b, 0, chan_a, 10_000, &mut p_ctr), + + 0x50 => send_noret(0, 1, chan_a, 1_000, &mut p_ctr), + 0x51 => send_noret(1, 0, chan_a, 1_000, &mut p_ctr), + 0x52 => send_noret(1, 2, chan_b, 1_000, &mut p_ctr), + 0x53 => send_noret(2, 1, chan_b, 1_000, &mut p_ctr), + 0x54 => send_hop_noret(0, 1, chan_a, 2, chan_b, 1_000, &mut p_ctr), + 0x55 => send_hop_noret(2, 1, chan_b, 0, chan_a, 1_000, &mut p_ctr), + + 0x58 => send_noret(0, 1, chan_a, 100, &mut p_ctr), + 0x59 => send_noret(1, 0, chan_a, 100, &mut p_ctr), + 0x5a => send_noret(1, 2, chan_b, 100, &mut p_ctr), + 0x5b => send_noret(2, 1, chan_b, 100, &mut p_ctr), + 0x5c => send_hop_noret(0, 1, chan_a, 2, chan_b, 100, &mut p_ctr), + 0x5d => send_hop_noret(2, 1, chan_b, 0, chan_a, 100, &mut p_ctr), + + 0x60 => send_noret(0, 1, chan_a, 10, &mut p_ctr), + 0x61 => send_noret(1, 0, chan_a, 10, &mut p_ctr), + 0x62 => send_noret(1, 2, chan_b, 10, &mut p_ctr), + 0x63 => send_noret(2, 1, chan_b, 10, &mut p_ctr), + 0x64 => send_hop_noret(0, 1, chan_a, 2, chan_b, 10, &mut p_ctr), + 0x65 => send_hop_noret(2, 1, chan_b, 0, chan_a, 10, &mut p_ctr), + + 0x68 => send_noret(0, 1, chan_a, 1, &mut p_ctr), + 0x69 => send_noret(1, 0, chan_a, 1, &mut p_ctr), + 0x6a => send_noret(1, 2, chan_b, 1, &mut p_ctr), + 0x6b => send_noret(2, 1, chan_b, 1, &mut p_ctr), + 0x6c => send_hop_noret(0, 1, chan_a, 2, chan_b, 1, &mut p_ctr), + 0x6d => send_hop_noret(2, 1, chan_b, 0, chan_a, 1, &mut p_ctr), 0x80 => { let mut max_feerate = last_htlc_clear_fee_a; @@ -2256,12 +2239,12 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { // Finally, make sure that at least one end of each channel can make a substantial payment assert!( - send_payment(&nodes[0], &nodes[1], chan_a, 10_000_000, &mut p_ctr) - || send_payment(&nodes[1], &nodes[0], chan_a, 10_000_000, &mut p_ctr) + send(0, 1, chan_a, 10_000_000, &mut p_ctr) + || send(1, 0, chan_a, 10_000_000, &mut p_ctr) ); assert!( - send_payment(&nodes[1], &nodes[2], chan_b, 10_000_000, &mut p_ctr) - || send_payment(&nodes[2], &nodes[1], chan_b, 10_000_000, &mut p_ctr) + send(1, 2, chan_b, 10_000_000, &mut p_ctr) + || send(2, 1, chan_b, 10_000_000, &mut p_ctr) ); last_htlc_clear_fee_a = fee_est_a.ret_val.load(atomic::Ordering::Acquire); From 246083e036dae7d7fbc492758d0a42af1f3cab69 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 19 Jan 2026 10:58:52 +0100 Subject: [PATCH 6/6] fuzz: track pending and resolved payments Track payment lifecycle by maintaining pending_payments and resolved_payments arrays per node: - When sending a payment, add its PaymentId to pending_payments - On PaymentSent/PaymentFailed events, move the PaymentId from pending to resolved (or assert it was already resolved for duplicate events) This tracking enables verifying payment state consistency after node restarts. Co-Authored-By: Claude Opus 4.5 Co-Authored-By: Matt Corallo --- fuzz/src/chanmon_consistency.rs | 51 ++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 1bcb6697a86..b5f70a350b2 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1109,6 +1109,9 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let mut node_b_ser = nodes[1].encode(); let mut node_c_ser = nodes[2].encode(); + let pending_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]); + let resolved_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]); + macro_rules! test_return { () => {{ assert_eq!(nodes[0].list_channels().len(), 1); @@ -1508,6 +1511,8 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let mut claim_set = new_hash_map(); let mut events = nodes[$node].get_and_clear_pending_events(); let had_events = !events.is_empty(); + let mut pending_payments = pending_payments.borrow_mut(); + let mut resolved_payments = resolved_payments.borrow_mut(); for event in events.drain(..) { match event { events::Event::PaymentClaimable { payment_hash, .. } => { @@ -1519,11 +1524,32 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { } } }, - events::Event::PaymentSent { .. } => {}, + events::Event::PaymentSent { payment_id, .. } => { + let sent_id = payment_id.unwrap(); + let idx_opt = + pending_payments[$node].iter().position(|id| *id == sent_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payments[$node].push(sent_id); + } else { + assert!(resolved_payments[$node].contains(&sent_id)); + } + }, + events::Event::PaymentFailed { payment_id, .. } => { + let idx_opt = + pending_payments[$node].iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payments[$node].push(payment_id); + } else if !resolved_payments[$node].contains(&payment_id) { + // Payment failed immediately on send, so it was never added to + // pending_payments. Add it to resolved_payments to track it. + resolved_payments[$node].push(payment_id); + } + }, events::Event::PaymentClaimed { .. } => {}, events::Event::PaymentPathSuccessful { .. } => {}, events::Event::PaymentPathFailed { .. } => {}, - events::Event::PaymentFailed { .. } => {}, events::Event::ProbeSuccessful { .. } | events::Event::ProbeFailed { .. } => { // Even though we don't explicitly send probes, because probes are @@ -1619,7 +1645,11 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - send_payment(source, dest, dest_chan_id, amt, secret, hash, id) + let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); + if succeeded { + pending_payments.borrow_mut()[source_idx].push(id); + } + succeeded }; let send_noret = |source_idx, dest_idx, dest_chan_id, amt, payment_ctr: &mut u64| { send(source_idx, dest_idx, dest_chan_id, amt, payment_ctr); @@ -1638,7 +1668,20 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - send_hop_payment(source, middle, middle_scid, dest, dest_scid, amt, secret, hash, id); + let succeeded = send_hop_payment( + source, + middle, + middle_scid, + dest, + dest_scid, + amt, + secret, + hash, + id, + ); + if succeeded { + pending_payments.borrow_mut()[source_idx].push(id); + } }; let v = get_slice!(1)[0];