fuzz: add transaction broadcast assertions
What changed, and why it matters
This commit only adds extra checks inside a fuzz test harness. It makes the test's fake transaction broadcaster record any transactions it is asked to broadcast, clears that record after initial channel setup, and then asserts that no further broadcasts happen during normal fuzzing operation. It does not change production code, user-facing behavior, or fix a known bug.
No action required; treat as a normal test-harness improvement. If reviewing, verify the assertion does not make the fuzz target overly restrictive by rejecting legitimate failure-recovery broadcasts.
Security signals we found
Adds an invariant assertion in fuzzing: no unexpected transaction broadcasts during normal operation
Only affects test/fuzz infrastructure, not production consensus or P2P code
No change to cryptographic, networking, or channel-state logic
Evidence from the diff
The change is confined to fuzz/src/chanmon_consistency.rs. TestBroadcaster now stores cloned Transactions in a RefCell
Changed components
fuzz/src/chanmon_consistency.rsTestBroadcaster fuzz mockInspect captured patch +23 / −8
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index de83f80..7cc7986 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<Vec<Transaction>>,
+}
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<u8>);
@@ -334,7 +340,7 @@ impl chain::Watch<TestChannelSigner> 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<Out: Output>(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<Out: Output>(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<Out: Output>(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;
}};
}
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.