chanmon_consistency: assert claimed payments result in PaymentSent
What changed, and why it matters
This commit only adds a new safety check inside an internal fuzz test harness. It does not change production code, user-facing behavior, or network protocol handling. The change makes the test suite verify that when a payment is claimed by a receiver, the sender eventually receives a matching 'payment sent' confirmation. It is a test-quality improvement, not a security fix or vulnerability.
No action needed. Treat as a normal test-harness improvement. If reviewing, confirm the assertion logic correctly matches PaymentHash values and does not introduce false positives in fuzz runs.
Security signals we found
Adds invariant assertion in fuzz test only
No changes to cryptographic, networking, or state-machine code
No privilege boundary or input validation changes
No bug class being fixed in shipped code
Evidence from the diff
The patch modifies fuzz/src/chanmon_consistency.rs, a Lightning Dev Kit fuzzing target. It replaces a Vec
Changed components
fuzz/src/chanmon_consistency.rsInspect captured patch +26 / −9
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index 21623fd..573a2fd 100644
--- a/fuzz/src/chanmon_consistency.rs
+++ b/fuzz/src/chanmon_consistency.rs
@@ -1356,7 +1356,9 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
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()]);
+ let resolved_payments: RefCell<[HashMap<PaymentId, Option<PaymentHash>>; 3]> =
+ RefCell::new([new_hash_map(), new_hash_map(), new_hash_map()]);
+ let claimed_payment_hashes: RefCell<HashSet<PaymentHash>> = RefCell::new(HashSet::new());
macro_rules! test_return {
() => {{
@@ -1864,18 +1866,19 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
nodes[$node].fail_htlc_backwards(&payment_hash);
} else {
nodes[$node].claim_funds(PaymentPreimage(payment_hash.0));
+ claimed_payment_hashes.borrow_mut().insert(payment_hash);
}
}
},
- events::Event::PaymentSent { payment_id, .. } => {
+ events::Event::PaymentSent { payment_id, payment_hash, .. } => {
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);
+ resolved_payments[$node].insert(sent_id, Some(payment_hash));
} else {
- assert!(resolved_payments[$node].contains(&sent_id));
+ assert!(resolved_payments[$node].contains_key(&sent_id));
}
},
// Even though we don't explicitly send probes, because probes are
@@ -1887,9 +1890,9 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
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);
+ resolved_payments[$node].insert(payment_id, None);
} else {
- assert!(resolved_payments[$node].contains(&payment_id));
+ assert!(resolved_payments[$node].contains_key(&payment_id));
}
},
events::Event::PaymentFailed { payment_id, .. }
@@ -1898,11 +1901,11 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
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) {
+ resolved_payments[$node].insert(payment_id, None);
+ } else if !resolved_payments[$node].contains_key(&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);
+ resolved_payments[$node].insert(payment_id, None);
}
},
events::Event::PaymentClaimed { .. } => {},
@@ -2705,6 +2708,20 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
);
}
+ // Verify that every payment claimed by a receiver resulted in a
+ // PaymentSent event at the sender.
+ let resolved = resolved_payments.borrow();
+ for hash in claimed_payment_hashes.borrow().iter() {
+ let found = resolved.iter().any(|node_resolved| {
+ node_resolved.values().any(|h| h.as_ref() == Some(hash))
+ });
+ assert!(
+ found,
+ "Payment {:?} was claimed by receiver but sender never got PaymentSent",
+ hash
+ );
+ }
+
// Finally, make sure that at least one end of each channel can make a substantial payment
for &chan_id in &chan_ab_ids {
assert!(
Why this scored 12/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.