From 197e124c6226ece4c45ab06d90dd5238c7c0d2a1 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:45:06 +0200 Subject: [PATCH 01/11] Extract chanmon bootstrap helpers Extract the repeated peer-connection and channel-funding setup into small helpers. This leaves the fuzz scenario setup behavior unchanged while making later harness refactors easier to review. --- fuzz/src/chanmon_consistency.rs | 565 +++++++++++++++++--------------- 1 file changed, 299 insertions(+), 266 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index d678d97918f..3d4c4bbd865 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -936,12 +936,240 @@ fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) { ); } +#[derive(Copy, Clone)] enum ChanType { Legacy, KeyedAnchors, ZeroFeeCommitments, } +fn build_node_config(chan_type: ChanType) -> UserConfig { + let mut config = UserConfig::default(); + config.channel_config.forwarding_fee_proportional_millionths = 0; + config.channel_handshake_config.announce_for_forwarding = true; + config.reject_inbound_splices = false; + match chan_type { + ChanType::Legacy => { + config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; + }, + ChanType::KeyedAnchors => { + config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; + }, + ChanType::ZeroFeeCommitments => { + config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + }, + } + config +} + +fn complete_all_pending_monitor_updates(monitor: &Arc) { + for (channel_id, state) in monitor.latest_monitors.lock().unwrap().iter_mut() { + for (id, data) in state.pending_monitors.drain(..) { + monitor.chain_monitor.channel_monitor_updated(*channel_id, id).unwrap(); + if id >= state.persisted_monitor_id { + state.persisted_monitor_id = id; + state.persisted_monitor = data; + } + } + } +} + +fn connect_peers(source: &ChanMan<'_>, dest: &ChanMan<'_>) { + let init_dest = + Init { features: dest.init_features(), networks: None, remote_network_address: None }; + source.peer_connected(dest.get_our_node_id(), &init_dest, true).unwrap(); + let init_src = + Init { features: source.init_features(), networks: None, remote_network_address: None }; + dest.peer_connected(source.get_our_node_id(), &init_src, false).unwrap(); +} + +fn make_channel( + source: &ChanMan<'_>, dest: &ChanMan<'_>, source_monitor: &Arc, + dest_monitor: &Arc, dest_keys_manager: &Arc, chan_id: i32, + trusted_open: bool, trusted_accept: bool, chain_state: &mut ChainState, +) { + if trusted_open { + source + .create_channel_to_trusted_peer_0reserve( + dest.get_our_node_id(), + 100_000, + 42, + 0, + None, + None, + ) + .unwrap(); + } else { + 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(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendOpenChannel { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + + dest.handle_open_channel(source.get_our_node_id(), &open_channel); + let accept_channel = { + let events = dest.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::OpenChannelRequest { + ref temporary_channel_id, + ref counterparty_node_id, + .. + } = events[0] + { + let mut random_bytes = [0u8; 16]; + random_bytes.copy_from_slice(&dest_keys_manager.get_secure_random_bytes()[..16]); + let user_channel_id = u128::from_be_bytes(random_bytes); + if trusted_accept { + dest.accept_inbound_channel_from_trusted_peer( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + TrustedChannelFeatures::ZeroReserve, + None, + ) + .unwrap(); + } else { + dest.accept_inbound_channel( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + None, + ) + .unwrap(); + } + } else { + panic!("Wrong event type"); + } + let events = dest.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + + source.handle_accept_channel(dest.get_our_node_id(), &accept_channel); + { + let mut events = source.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::FundingGenerationReady { + temporary_channel_id, + channel_value_satoshis, + output_script, + .. + } = events.pop().unwrap() + { + let tx = Transaction { + version: Version(chan_id), + lock_time: LockTime::ZERO, + input: Vec::new(), + output: vec![TxOut { + value: Amount::from_sat(channel_value_satoshis), + script_pubkey: output_script, + }], + }; + source + .funding_transaction_generated( + temporary_channel_id, + dest.get_our_node_id(), + tx.clone(), + ) + .unwrap(); + chain_state.confirm_tx(tx); + } else { + panic!("Wrong event type"); + } + } + + let funding_created = { + let events = source.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendFundingCreated { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + dest.handle_funding_created(source.get_our_node_id(), &funding_created); + // Complete any pending monitor updates for dest after watch_channel. + complete_all_pending_monitor_updates(dest_monitor); + + let (funding_signed, channel_id) = { + let events = dest.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendFundingSigned { ref msg, .. } = events[0] { + (msg.clone(), msg.channel_id) + } else { + panic!("Wrong event type"); + } + }; + let events = dest.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::ChannelPending { ref counterparty_node_id, .. } = events[0] { + assert_eq!(counterparty_node_id, &source.get_our_node_id()); + } else { + panic!("Wrong event type"); + } + + source.handle_funding_signed(dest.get_our_node_id(), &funding_signed); + // Complete any pending monitor updates for source after watch_channel. + complete_all_pending_monitor_updates(source_monitor); + + let events = source.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::ChannelPending { + ref counterparty_node_id, + channel_id: ref event_channel_id, + .. + } = events[0] + { + assert_eq!(counterparty_node_id, &dest.get_our_node_id()); + assert_eq!(*event_channel_id, channel_id); + } else { + panic!("Wrong event type"); + } +} + +fn lock_fundings(nodes: &[ChanMan<'_>; 3]) { + let mut node_events = Vec::new(); + for node in nodes.iter() { + node_events.push(node.get_and_clear_pending_msg_events()); + } + for (idx, node_event) in node_events.iter().enumerate() { + for event in node_event { + if let MessageSendEvent::SendChannelReady { ref node_id, ref msg } = event { + for node in nodes.iter() { + if node.get_our_node_id() == *node_id { + node.handle_channel_ready(nodes[idx].get_our_node_id(), msg); + } + } + } else { + panic!("Wrong event type"); + } + } + } + + for node in nodes.iter() { + let events = node.get_and_clear_pending_msg_events(); + for event in events { + if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { + } else { + panic!("Wrong event type"); + } + } + } +} + #[inline] pub fn do_test(data: &[u8], out: Out) { let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); @@ -1005,24 +1233,6 @@ pub fn do_test(data: &[u8], out: Out) { Arc::clone(&keys_manager), )); - let mut config = UserConfig::default(); - config.channel_config.forwarding_fee_proportional_millionths = 0; - config.channel_handshake_config.announce_for_forwarding = true; - config.reject_inbound_splices = false; - match chan_type { - ChanType::Legacy => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - }, - ChanType::KeyedAnchors => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - }, - ChanType::ZeroFeeCommitments => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - }, - } let network = Network::Bitcoin; let best_block_timestamp = genesis_block(network).header.time; let params = @@ -1038,7 +1248,7 @@ pub fn do_test(data: &[u8], out: Out) { keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), - config, + build_node_config(chan_type), params, best_block_timestamp, ), @@ -1069,25 +1279,6 @@ pub fn do_test(data: &[u8], out: Out) { Arc::clone(keys), )); - let mut config = UserConfig::default(); - config.channel_config.forwarding_fee_proportional_millionths = 0; - config.channel_handshake_config.announce_for_forwarding = true; - config.reject_inbound_splices = false; - match chan_type { - ChanType::Legacy => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - }, - ChanType::KeyedAnchors => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false; - }, - ChanType::ZeroFeeCommitments => { - config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; - config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - }, - } - let mut monitors = new_hash_map(); let mut old_monitors = old_monitors.latest_monitors.lock().unwrap(); for (channel_id, mut prev_state) in old_monitors.drain() { @@ -1137,7 +1328,7 @@ pub fn do_test(data: &[u8], out: Out) { router: &router, message_router: &router, logger, - config, + config: build_node_config(chan_type), channel_monitors: monitor_refs, }; @@ -1154,224 +1345,6 @@ pub fn do_test(data: &[u8], out: Out) { res }; - macro_rules! complete_all_pending_monitor_updates { - ($monitor: expr) => {{ - for (channel_id, state) in $monitor.latest_monitors.lock().unwrap().iter_mut() { - for (id, data) in state.pending_monitors.drain(..) { - $monitor.chain_monitor.channel_monitor_updated(*channel_id, id).unwrap(); - if id >= state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } - } - } - }}; - } - macro_rules! connect_peers { - ($source: expr, $dest: expr) => {{ - let init_dest = Init { - features: $dest.init_features(), - networks: None, - remote_network_address: None, - }; - $source.peer_connected($dest.get_our_node_id(), &init_dest, true).unwrap(); - let init_src = Init { - features: $source.init_features(), - networks: None, - 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, $trusted_open: expr, $trusted_accept: expr) => {{ - if $trusted_open { - $source - .create_channel_to_trusted_peer_0reserve( - $dest.get_our_node_id(), - 100_000, - 42, - 0, - None, - None, - ) - .unwrap(); - } else { - $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(); - assert_eq!(events.len(), 1); - if let MessageSendEvent::SendOpenChannel { ref msg, .. } = events[0] { - msg.clone() - } else { - panic!("Wrong event type"); - } - }; - - $dest.handle_open_channel($source.get_our_node_id(), &open_channel); - let accept_channel = { - let events = $dest.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - if let events::Event::OpenChannelRequest { - ref temporary_channel_id, - ref counterparty_node_id, - .. - } = events[0] - { - let mut random_bytes = [0u8; 16]; - random_bytes - .copy_from_slice(&$dest_keys_manager.get_secure_random_bytes()[..16]); - let user_channel_id = u128::from_be_bytes(random_bytes); - if $trusted_accept { - $dest - .accept_inbound_channel_from_trusted_peer( - temporary_channel_id, - counterparty_node_id, - user_channel_id, - TrustedChannelFeatures::ZeroReserve, - None, - ) - .unwrap(); - } else { - $dest - .accept_inbound_channel( - temporary_channel_id, - counterparty_node_id, - user_channel_id, - None, - ) - .unwrap(); - } - } else { - panic!("Wrong event type"); - } - let events = $dest.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - if let MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] { - msg.clone() - } else { - panic!("Wrong event type"); - } - }; - - $source.handle_accept_channel($dest.get_our_node_id(), &accept_channel); - { - let mut events = $source.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - if let events::Event::FundingGenerationReady { - temporary_channel_id, - channel_value_satoshis, - output_script, - .. - } = events.pop().unwrap() - { - let tx = Transaction { - version: Version($chan_id), - lock_time: LockTime::ZERO, - input: Vec::new(), - output: vec![TxOut { - value: Amount::from_sat(channel_value_satoshis), - script_pubkey: output_script, - }], - }; - $source - .funding_transaction_generated( - temporary_channel_id, - $dest.get_our_node_id(), - tx.clone(), - ) - .unwrap(); - chain_state.confirm_tx(tx); - } else { - panic!("Wrong event type"); - } - } - - let funding_created = { - let events = $source.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - if let MessageSendEvent::SendFundingCreated { ref msg, .. } = events[0] { - msg.clone() - } else { - panic!("Wrong event type"); - } - }; - $dest.handle_funding_created($source.get_our_node_id(), &funding_created); - // Complete any pending monitor updates for dest after watch_channel - complete_all_pending_monitor_updates!($dest_monitor); - - let (funding_signed, channel_id) = { - let events = $dest.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - if let MessageSendEvent::SendFundingSigned { ref msg, .. } = events[0] { - (msg.clone(), msg.channel_id.clone()) - } else { - panic!("Wrong event type"); - } - }; - let events = $dest.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - if let events::Event::ChannelPending { ref counterparty_node_id, .. } = events[0] { - assert_eq!(counterparty_node_id, &$source.get_our_node_id()); - } else { - panic!("Wrong event type"); - } - - $source.handle_funding_signed($dest.get_our_node_id(), &funding_signed); - // Complete any pending monitor updates for source after watch_channel - complete_all_pending_monitor_updates!($source_monitor); - - let events = $source.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - if let events::Event::ChannelPending { - ref counterparty_node_id, - channel_id: ref event_channel_id, - .. - } = events[0] - { - assert_eq!(counterparty_node_id, &$dest.get_our_node_id()); - assert_eq!(*event_channel_id, channel_id); - } else { - panic!("Wrong event type"); - } - }}; - } - - macro_rules! lock_fundings { - ($nodes: expr) => {{ - let mut node_events = Vec::new(); - for node in $nodes.iter() { - node_events.push(node.get_and_clear_pending_msg_events()); - } - for (idx, node_event) in node_events.iter().enumerate() { - for event in node_event { - if let MessageSendEvent::SendChannelReady { ref node_id, ref msg } = event { - for node in $nodes.iter() { - if node.get_our_node_id() == *node_id { - node.handle_channel_ready($nodes[idx].get_our_node_id(), msg); - } - } - } else { - panic!("Wrong event type"); - } - } - } - - for node in $nodes.iter() { - let events = node.get_and_clear_pending_msg_events(); - for event in events { - if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { - } else { - panic!("Wrong event type"); - } - } - } - }}; - } - let wallet_a = TestWalletSource::new(SecretKey::from_slice(&[1; 32]).unwrap()); let wallet_b = TestWalletSource::new(SecretKey::from_slice(&[2; 32]).unwrap()); let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); @@ -1413,8 +1386,8 @@ pub fn do_test(data: &[u8], out: Out) { let fee_estimators = [Arc::clone(&fee_est_a), Arc::clone(&fee_est_b), Arc::clone(&fee_est_c)]; // Connect peers first, then create channels - connect_peers!(nodes[0], nodes[1]); - connect_peers!(nodes[1], nodes[2]); + 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). // @@ -1422,14 +1395,74 @@ pub fn do_test(data: &[u8], out: Out) { // txid and funding outpoint. // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept) - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false); - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 2, true, true); - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 3, false, true); + make_channel( + &nodes[0], + &nodes[1], + &monitor_a, + &monitor_b, + &keys_manager_b, + 1, + false, + false, + &mut chain_state, + ); + make_channel( + &nodes[0], + &nodes[1], + &monitor_a, + &monitor_b, + &keys_manager_b, + 2, + true, + true, + &mut chain_state, + ); + make_channel( + &nodes[0], + &nodes[1], + &monitor_a, + &monitor_b, + &keys_manager_b, + 3, + false, + true, + &mut chain_state, + ); // B-C: channel 4 B has 0-reserve (via trusted accept), // channel 5 C has 0-reserve (via trusted open) - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 4, false, true); - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 5, true, false); - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 6, false, false); + make_channel( + &nodes[1], + &nodes[2], + &monitor_b, + &monitor_c, + &keys_manager_c, + 4, + false, + true, + &mut chain_state, + ); + make_channel( + &nodes[1], + &nodes[2], + &monitor_b, + &monitor_c, + &keys_manager_c, + 5, + true, + false, + &mut chain_state, + ); + make_channel( + &nodes[1], + &nodes[2], + &monitor_b, + &monitor_c, + &keys_manager_c, + 6, + false, + false, + &mut chain_state, + ); // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions // during normal operation in `test_return`. @@ -1463,7 +1496,7 @@ pub fn do_test(data: &[u8], out: Out) { sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None); sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None); - lock_fundings!(nodes); + lock_fundings(&nodes); // Get channel IDs for all A-B channels (from node A's perspective) let chan_ab_ids = { From de2bf6175ac3b2a7cf6607d72b0bb4c283758df5 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:31:38 +0200 Subject: [PATCH 02/11] Wrap chanmon nodes in HarnessNode Introduce a small wrapper around each channel manager and its test resources. This keeps node-local state together before moving more operations onto the harness. --- fuzz/src/chanmon_consistency.rs | 158 +++++++++++++------------------- 1 file changed, 66 insertions(+), 92 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 3d4c4bbd865..afdebabfcde 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -943,6 +943,34 @@ enum ChanType { ZeroFeeCommitments, } +struct HarnessNode<'a> { + node: ChanMan<'a>, + monitor: Arc, + keys_manager: Arc, +} + +impl<'a> std::ops::Deref for HarnessNode<'a> { + type Target = ChanMan<'a>; + + fn deref(&self) -> &Self::Target { + &self.node + } +} + +impl<'a> HarnessNode<'a> { + fn complete_all_pending_monitor_updates(&self) { + for (channel_id, state) in self.monitor.latest_monitors.lock().unwrap().iter_mut() { + for (id, data) in state.pending_monitors.drain(..) { + self.monitor.chain_monitor.channel_monitor_updated(*channel_id, id).unwrap(); + if id >= state.persisted_monitor_id { + state.persisted_monitor_id = id; + state.persisted_monitor = data; + } + } + } + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -965,18 +993,6 @@ fn build_node_config(chan_type: ChanType) -> UserConfig { config } -fn complete_all_pending_monitor_updates(monitor: &Arc) { - for (channel_id, state) in monitor.latest_monitors.lock().unwrap().iter_mut() { - for (id, data) in state.pending_monitors.drain(..) { - monitor.chain_monitor.channel_monitor_updated(*channel_id, id).unwrap(); - if id >= state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } - } - } -} - fn connect_peers(source: &ChanMan<'_>, dest: &ChanMan<'_>) { let init_dest = Init { features: dest.init_features(), networks: None, remote_network_address: None }; @@ -987,9 +1003,8 @@ fn connect_peers(source: &ChanMan<'_>, dest: &ChanMan<'_>) { } fn make_channel( - source: &ChanMan<'_>, dest: &ChanMan<'_>, source_monitor: &Arc, - dest_monitor: &Arc, dest_keys_manager: &Arc, chan_id: i32, - trusted_open: bool, trusted_accept: bool, chain_state: &mut ChainState, + source: &HarnessNode<'_>, dest: &HarnessNode<'_>, chan_id: i32, trusted_open: bool, + trusted_accept: bool, chain_state: &mut ChainState, ) { if trusted_open { source @@ -1026,7 +1041,7 @@ fn make_channel( } = events[0] { let mut random_bytes = [0u8; 16]; - random_bytes.copy_from_slice(&dest_keys_manager.get_secure_random_bytes()[..16]); + random_bytes.copy_from_slice(&dest.keys_manager.get_secure_random_bytes()[..16]); let user_channel_id = u128::from_be_bytes(random_bytes); if trusted_accept { dest.accept_inbound_channel_from_trusted_peer( @@ -1102,7 +1117,7 @@ fn make_channel( }; dest.handle_funding_created(source.get_our_node_id(), &funding_created); // Complete any pending monitor updates for dest after watch_channel. - complete_all_pending_monitor_updates(dest_monitor); + dest.complete_all_pending_monitor_updates(); let (funding_signed, channel_id) = { let events = dest.get_and_clear_pending_msg_events(); @@ -1123,7 +1138,7 @@ fn make_channel( source.handle_funding_signed(dest.get_our_node_id(), &funding_signed); // Complete any pending monitor updates for source after watch_channel. - complete_all_pending_monitor_updates(source_monitor); + source.complete_all_pending_monitor_updates(); let events = source.get_and_clear_pending_events(); assert_eq!(events.len(), 1); @@ -1140,7 +1155,7 @@ fn make_channel( } } -fn lock_fundings(nodes: &[ChanMan<'_>; 3]) { +fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { let mut node_events = Vec::new(); for node in nodes.iter() { node_events.push(node.get_and_clear_pending_msg_events()); @@ -1379,7 +1394,23 @@ pub fn do_test(data: &[u8], out: Out) { let (node_b, mut monitor_b, keys_manager_b, logger_b) = make_node!(1, fee_est_b, broadcast_b); let (node_c, mut monitor_c, keys_manager_c, logger_c) = make_node!(2, fee_est_c, broadcast_c); - let mut nodes = [node_a, node_b, node_c]; + let mut nodes = [ + HarnessNode { + node: node_a, + monitor: Arc::clone(&monitor_a), + keys_manager: Arc::clone(&keys_manager_a), + }, + HarnessNode { + node: node_b, + monitor: Arc::clone(&monitor_b), + keys_manager: Arc::clone(&keys_manager_b), + }, + HarnessNode { + node: node_c, + monitor: Arc::clone(&monitor_c), + keys_manager: Arc::clone(&keys_manager_c), + }, + ]; #[allow(unused_variables)] let loggers = [logger_a, logger_b, logger_c]; #[allow(unused_variables)] @@ -1395,74 +1426,14 @@ pub fn do_test(data: &[u8], out: Out) { // txid and funding outpoint. // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept) - make_channel( - &nodes[0], - &nodes[1], - &monitor_a, - &monitor_b, - &keys_manager_b, - 1, - false, - false, - &mut chain_state, - ); - make_channel( - &nodes[0], - &nodes[1], - &monitor_a, - &monitor_b, - &keys_manager_b, - 2, - true, - true, - &mut chain_state, - ); - make_channel( - &nodes[0], - &nodes[1], - &monitor_a, - &monitor_b, - &keys_manager_b, - 3, - false, - true, - &mut chain_state, - ); + make_channel(&nodes[0], &nodes[1], 1, false, false, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 2, true, true, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 3, false, true, &mut chain_state); // B-C: channel 4 B has 0-reserve (via trusted accept), // channel 5 C has 0-reserve (via trusted open) - make_channel( - &nodes[1], - &nodes[2], - &monitor_b, - &monitor_c, - &keys_manager_c, - 4, - false, - true, - &mut chain_state, - ); - make_channel( - &nodes[1], - &nodes[2], - &monitor_b, - &monitor_c, - &keys_manager_c, - 5, - true, - false, - &mut chain_state, - ); - make_channel( - &nodes[1], - &nodes[2], - &monitor_b, - &monitor_c, - &keys_manager_c, - 6, - false, - false, - &mut chain_state, - ); + make_channel(&nodes[1], &nodes[2], 4, false, true, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 5, true, false, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions // during normal operation in `test_return`. @@ -2656,8 +2627,9 @@ pub fn do_test(data: &[u8], out: Out) { &fee_est_a, broadcast_a.clone(), ); - nodes[0] = new_node_a; - monitor_a = new_monitor_a; + nodes[0].node = new_node_a; + monitor_a = Arc::clone(&new_monitor_a); + nodes[0].monitor = new_monitor_a; }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on @@ -2685,8 +2657,9 @@ pub fn do_test(data: &[u8], out: Out) { &fee_est_b, broadcast_b.clone(), ); - nodes[1] = new_node_b; - monitor_b = new_monitor_b; + nodes[1].node = new_node_b; + monitor_b = Arc::clone(&new_monitor_b); + nodes[1].monitor = new_monitor_b; }, 0xbc | 0xbd | 0xbe => { // Restart node C, picking among the in-flight `ChannelMonitor`s to use based on @@ -2710,8 +2683,9 @@ pub fn do_test(data: &[u8], out: Out) { &fee_est_c, broadcast_c.clone(), ); - nodes[2] = new_node_c; - monitor_c = new_monitor_c; + nodes[2].node = new_node_c; + monitor_c = Arc::clone(&new_monitor_c); + nodes[2].monitor = new_monitor_c; }, 0xc0 => keys_manager_a.disable_supported_ops_for_all_signers(), From 63a175ccc42221c786e8bcb48e8f3132364cbbd2 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 18:00:00 +0200 Subject: [PATCH 03/11] Build chanmon node resources Move construction of loggers, keys, monitors, broadcasters, wallets, and fee estimators into node resource setup. This removes ad hoc local closures while preserving the deterministic test inputs used by the fuzzer. --- fuzz/src/chanmon_consistency.rs | 669 ++++++++++++++++---------------- 1 file changed, 345 insertions(+), 324 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index afdebabfcde..2e842b2a44e 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -947,6 +947,10 @@ struct HarnessNode<'a> { node: ChanMan<'a>, monitor: Arc, keys_manager: Arc, + logger: Arc, + broadcaster: Arc, + fee_estimator: Arc, + wallet: TestWalletSource, } impl<'a> std::ops::Deref for HarnessNode<'a> { @@ -958,6 +962,72 @@ impl<'a> std::ops::Deref for HarnessNode<'a> { } impl<'a> HarnessNode<'a> { + fn build_loggers( + node_id: u8, out: &Out, + ) -> (Arc, Arc) { + let raw_logger = Arc::new(test_logger::TestLogger::new(node_id.to_string(), out.clone())); + let logger_for_monitor: Arc = raw_logger.clone(); + let logger: Arc = raw_logger; + (logger_for_monitor, logger) + } + + fn build_chain_monitor( + broadcaster: &Arc, fee_estimator: &Arc, + keys_manager: &Arc, logger_for_monitor: Arc, + persistence_style: ChannelMonitorUpdateStatus, + ) -> Arc { + Arc::new(TestChainMonitor::new( + Arc::clone(broadcaster), + logger_for_monitor, + Arc::clone(fee_estimator), + Arc::new(TestPersister { update_ret: Mutex::new(persistence_style) }), + Arc::clone(keys_manager), + )) + } + + fn new( + node_id: u8, wallet: TestWalletSource, fee_estimator: Arc, + broadcaster: Arc, persistence_style: ChannelMonitorUpdateStatus, + out: &Out, router: &'a FuzzRouter, chan_type: ChanType, + ) -> Self { + let (logger_for_monitor, logger) = Self::build_loggers(node_id, out); + let node_secret = SecretKey::from_slice(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, node_id, + ]) + .unwrap(); + let keys_manager = Arc::new(KeyProvider { + node_secret, + rand_bytes_id: atomic::AtomicU32::new(0), + enforcement_states: Mutex::new(new_hash_map()), + }); + let monitor = Self::build_chain_monitor( + &broadcaster, + &fee_estimator, + &keys_manager, + logger_for_monitor, + persistence_style, + ); + let network = Network::Bitcoin; + let best_block_timestamp = genesis_block(network).header.time; + let params = ChainParameters { network, best_block: BlockLocator::from_network(network) }; + let node = ChannelManager::new( + Arc::clone(&fee_estimator), + Arc::clone(&monitor), + Arc::clone(&broadcaster), + router, + router, + Arc::clone(&logger), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + build_node_config(chan_type), + params, + best_block_timestamp, + ); + Self { node, monitor, keys_manager, logger, broadcaster, fee_estimator, wallet } + } + fn complete_all_pending_monitor_updates(&self) { for (channel_id, state) in self.monitor.latest_monitors.lock().unwrap().iter_mut() { for (id, data) in state.pending_monitors.drain(..) { @@ -993,6 +1063,17 @@ fn build_node_config(chan_type: ChanType) -> UserConfig { config } +fn assert_test_invariants(nodes: &[HarnessNode<'_>; 3]) { + assert_eq!(nodes[0].list_channels().len(), 3); + assert_eq!(nodes[1].list_channels().len(), 6); + assert_eq!(nodes[2].list_channels().len(), 3); + + // All broadcasters should be empty. Broadcast transactions are handled explicitly. + assert!(nodes[0].broadcaster.txn_broadcasted.borrow().is_empty()); + assert!(nodes[1].broadcaster.txn_broadcasted.borrow().is_empty()); + assert!(nodes[2].broadcaster.txn_broadcasted.borrow().is_empty()); +} + fn connect_peers(source: &ChanMan<'_>, dest: &ChanMan<'_>) { let init_dest = Init { features: dest.init_features(), networks: None, remote_network_address: None }; @@ -1187,9 +1268,6 @@ fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { #[inline] pub fn do_test(data: &[u8], out: Out) { - let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); - let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); - let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); let router = FuzzRouter {}; // Read initial monitor styles and channel type from fuzz input byte 0: @@ -1223,163 +1301,26 @@ pub fn do_test(data: &[u8], out: Out) { let mut node_height_a: u32 = 0; let mut node_height_b: u32 = 0; let mut node_height_c: u32 = 0; - - macro_rules! make_node { - ($node_id: expr, $fee_estimator: expr, $broadcaster: expr) => {{ - let logger: Arc = - Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone())); - let node_secret = SecretKey::from_slice(&[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, $node_id, - ]) - .unwrap(); - let keys_manager = Arc::new(KeyProvider { - node_secret, - rand_bytes_id: atomic::AtomicU32::new(0), - enforcement_states: Mutex::new(new_hash_map()), - }); - let monitor = Arc::new(TestChainMonitor::new( - $broadcaster.clone(), - logger.clone(), - $fee_estimator.clone(), - Arc::new(TestPersister { - update_ret: Mutex::new(mon_style[$node_id as usize].borrow().clone()), - }), - Arc::clone(&keys_manager), - )); - - let network = Network::Bitcoin; - let best_block_timestamp = genesis_block(network).header.time; - let params = - ChainParameters { network, best_block: BlockLocator::from_network(network) }; - ( - ChannelManager::new( - $fee_estimator.clone(), - monitor.clone(), - $broadcaster.clone(), - &router, - &router, - Arc::clone(&logger), - keys_manager.clone(), - keys_manager.clone(), - keys_manager.clone(), - build_node_config(chan_type), - params, - best_block_timestamp, - ), - monitor, - keys_manager, - logger, - ) - }}; - } - - let reload_node = |ser: &Vec, - node_id: u8, - old_monitors: &TestChainMonitor, - mut use_old_mons, - keys, - fee_estimator, - broadcaster: Arc| { - let keys_manager = Arc::clone(keys); - let logger: Arc = - Arc::new(test_logger::TestLogger::new(node_id.to_string(), out.clone())); - let chain_monitor = Arc::new(TestChainMonitor::new( - broadcaster.clone(), - logger.clone(), - Arc::clone(fee_estimator), - Arc::new(TestPersister { - update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed), - }), - Arc::clone(keys), - )); - - let mut monitors = new_hash_map(); - let mut old_monitors = old_monitors.latest_monitors.lock().unwrap(); - for (channel_id, mut prev_state) in old_monitors.drain() { - let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { - // Reload with the oldest `ChannelMonitor` (the one that we already told - // `ChannelManager` we finished persisting). - (prev_state.persisted_monitor_id, prev_state.persisted_monitor) - } else if use_old_mons % 3 == 1 { - // Reload with the second-oldest `ChannelMonitor` - let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); - prev_state.pending_monitors.drain(..).next().unwrap_or(old_mon) - } else { - // Reload with the newest `ChannelMonitor` - let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); - prev_state.pending_monitors.pop().unwrap_or(old_mon) - }; - // Use a different value of `use_old_mons` if we have another monitor (only for node B) - // by shifting `use_old_mons` one in base-3. - use_old_mons /= 3; - let mon = <(BlockLocator, ChannelMonitor)>::read( - &mut &serialized_mon[..], - (&**keys, &**keys), - ) - .expect("Failed to read monitor"); - monitors.insert(channel_id, mon.1); - // Update the latest `ChannelMonitor` state to match what we just told LDK. - prev_state.persisted_monitor = serialized_mon; - prev_state.persisted_monitor_id = mon_id; - // Wipe any `ChannelMonitor`s which we never told LDK we finished persisting, - // considering them discarded. LDK should replay these for us as they're stored in - // the `ChannelManager`. - prev_state.pending_monitors.clear(); - chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); - } - let mut monitor_refs = new_hash_map(); - for (channel_id, monitor) in monitors.iter() { - monitor_refs.insert(*channel_id, monitor); - } - - let read_args = ChannelManagerReadArgs { - entropy_source: Arc::clone(&keys_manager), - node_signer: Arc::clone(&keys_manager), - signer_provider: keys_manager, - fee_estimator: Arc::clone(fee_estimator), - chain_monitor: chain_monitor.clone(), - tx_broadcaster: broadcaster, - router: &router, - message_router: &router, - logger, - config: build_node_config(chan_type), - channel_monitors: monitor_refs, - }; - - let manager = <(BlockLocator, ChanMan)>::read(&mut &ser[..], read_args) - .expect("Failed to read manager"); - let res = (manager.1, chain_monitor.clone()); - for (channel_id, mon) in monitors.drain() { - assert_eq!( - chain_monitor.chain_monitor.watch_channel(channel_id, mon), - Ok(ChannelMonitorUpdateStatus::Completed) - ); - } - *chain_monitor.persister.update_ret.lock().unwrap() = *mon_style[node_id as usize].borrow(); - res - }; - let wallet_a = TestWalletSource::new(SecretKey::from_slice(&[1; 32]).unwrap()); let wallet_b = TestWalletSource::new(SecretKey::from_slice(&[2; 32]).unwrap()); let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); - let wallets = vec![wallet_a, wallet_b, wallet_c]; + let wallets = [&wallet_a, &wallet_b, &wallet_c]; let coinbase_tx = bitcoin::Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO, input: vec![bitcoin::TxIn { ..Default::default() }], output: wallets .iter() - .map(|w| TxOut { + .map(|wallet| TxOut { value: Amount::from_sat(100_000), - script_pubkey: w.get_change_script().unwrap(), + script_pubkey: wallet.get_change_script().unwrap(), }) .collect(), }; - wallets.iter().enumerate().for_each(|(i, w)| { - w.add_utxo(coinbase_tx.clone(), i as u32); - }); + for (idx, wallet) in wallets.iter().enumerate() { + wallet.add_utxo(coinbase_tx.clone(), idx as u32); + } let fee_est_a = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); let mut last_htlc_clear_fee_a = 253; @@ -1387,34 +1328,50 @@ pub fn do_test(data: &[u8], out: Out) { let mut last_htlc_clear_fee_b = 253; let fee_est_c = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); let mut last_htlc_clear_fee_c = 253; + let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); + let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); + let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); // 3 nodes is enough to hit all the possible cases, notably unknown-source-unknown-dest // forwarding. - let (node_a, mut monitor_a, keys_manager_a, logger_a) = make_node!(0, fee_est_a, broadcast_a); - let (node_b, mut monitor_b, keys_manager_b, logger_b) = make_node!(1, fee_est_b, broadcast_b); - let (node_c, mut monitor_c, keys_manager_c, logger_c) = make_node!(2, fee_est_c, broadcast_c); - let mut nodes = [ - HarnessNode { - node: node_a, - monitor: Arc::clone(&monitor_a), - keys_manager: Arc::clone(&keys_manager_a), - }, - HarnessNode { - node: node_b, - monitor: Arc::clone(&monitor_b), - keys_manager: Arc::clone(&keys_manager_b), - }, - HarnessNode { - node: node_c, - monitor: Arc::clone(&monitor_c), - keys_manager: Arc::clone(&keys_manager_c), - }, + HarnessNode::new( + 0, + wallet_a, + Arc::clone(&fee_est_a), + Arc::clone(&broadcast_a), + mon_style[0].borrow().clone(), + &out, + &router, + chan_type, + ), + HarnessNode::new( + 1, + wallet_b, + Arc::clone(&fee_est_b), + Arc::clone(&broadcast_b), + mon_style[1].borrow().clone(), + &out, + &router, + chan_type, + ), + HarnessNode::new( + 2, + wallet_c, + Arc::clone(&fee_est_c), + Arc::clone(&broadcast_c), + mon_style[2].borrow().clone(), + &out, + &router, + chan_type, + ), ]; - #[allow(unused_variables)] - let loggers = [logger_a, logger_b, logger_c]; - #[allow(unused_variables)] - let fee_estimators = [Arc::clone(&fee_est_a), Arc::clone(&fee_est_b), Arc::clone(&fee_est_c)]; + let mut monitor_a = Arc::clone(&nodes[0].monitor); + let mut monitor_b = Arc::clone(&nodes[1].monitor); + let mut monitor_c = Arc::clone(&nodes[2].monitor); + let keys_manager_a = Arc::clone(&nodes[0].keys_manager); + let keys_manager_b = Arc::clone(&nodes[1].keys_manager); + let keys_manager_c = Arc::clone(&nodes[2].keys_manager); // Connect peers first, then create channels connect_peers(&nodes[0], &nodes[1]); @@ -1437,12 +1394,12 @@ pub fn do_test(data: &[u8], out: Out) { // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions // during normal operation in `test_return`. - broadcast_a.txn_broadcasted.borrow_mut().clear(); - broadcast_b.txn_broadcasted.borrow_mut().clear(); - broadcast_c.txn_broadcasted.borrow_mut().clear(); + nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); let sync_with_chain_state = |chain_state: &ChainState, - node: &ChannelManager<_, _, _, _, _, _, _, _, _>, + node: &HarnessNode<'_>, node_height: &mut u32, num_blocks: Option| { let target_height = if let Some(num_blocks) = num_blocks { @@ -1450,7 +1407,6 @@ pub fn do_test(data: &[u8], out: Out) { } else { chain_state.tip_height() }; - while *node_height < target_height { *node_height += 1; let (header, txn) = chain_state.block_at(*node_height); @@ -1463,9 +1419,9 @@ pub fn do_test(data: &[u8], out: Out) { }; // Sync all nodes to tip to lock the funding. - sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, None); - sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None); - sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None); + sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None); + sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None); + sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None); lock_fundings(&nodes); @@ -1505,20 +1461,93 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! test_return { () => {{ - assert_eq!(nodes[0].list_channels().len(), 3); - assert_eq!(nodes[1].list_channels().len(), 6); - assert_eq!(nodes[2].list_channels().len(), 3); - - // All broadcasters should be empty (all broadcast transactions should be handled - // explicitly). - assert!(broadcast_a.txn_broadcasted.borrow().is_empty()); - assert!(broadcast_b.txn_broadcasted.borrow().is_empty()); - assert!(broadcast_c.txn_broadcasted.borrow().is_empty()); - + assert_test_invariants(&nodes); return; }}; } + let reload_node = |ser: &Vec, + node_id: u8, + old_monitors: &TestChainMonitor, + mut use_old_mons, + keys: &Arc, + fee_estimator: &Arc, + broadcaster: Arc| { + let keys_manager = Arc::clone(keys); + let (logger_for_monitor, logger) = HarnessNode::build_loggers(node_id, &out); + let chain_monitor = HarnessNode::build_chain_monitor( + &broadcaster, + fee_estimator, + &keys_manager, + logger_for_monitor, + ChannelMonitorUpdateStatus::Completed, + ); + + let mut monitors = new_hash_map(); + let mut old_monitors = old_monitors.latest_monitors.lock().unwrap(); + for (channel_id, mut prev_state) in old_monitors.drain() { + let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { + // Reload with the oldest `ChannelMonitor` (the one that we already told + // `ChannelManager` we finished persisting). + (prev_state.persisted_monitor_id, prev_state.persisted_monitor) + } else if use_old_mons % 3 == 1 { + // Reload with the second-oldest `ChannelMonitor` + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.drain(..).next().unwrap_or(old_mon) + } else { + // Reload with the newest `ChannelMonitor` + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.pop().unwrap_or(old_mon) + }; + // Use a different value of `use_old_mons` if we have another monitor (only for node B) + // by shifting `use_old_mons` one in base-3. + use_old_mons /= 3; + let mon = <(BlockLocator, ChannelMonitor)>::read( + &mut &serialized_mon[..], + (&*keys_manager, &*keys_manager), + ) + .expect("Failed to read monitor"); + monitors.insert(channel_id, mon.1); + // Update the latest `ChannelMonitor` state to match what we just told LDK. + prev_state.persisted_monitor = serialized_mon; + prev_state.persisted_monitor_id = mon_id; + // Wipe any `ChannelMonitor`s which we never told LDK we finished persisting, + // considering them discarded. LDK should replay these for us as they're stored in + // the `ChannelManager`. + prev_state.pending_monitors.clear(); + chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); + } + let mut monitor_refs = new_hash_map(); + for (channel_id, monitor) in monitors.iter() { + monitor_refs.insert(*channel_id, monitor); + } + + let read_args = ChannelManagerReadArgs { + entropy_source: Arc::clone(&keys_manager), + node_signer: Arc::clone(&keys_manager), + signer_provider: Arc::clone(&keys_manager), + fee_estimator: Arc::clone(fee_estimator), + chain_monitor: chain_monitor.clone(), + tx_broadcaster: broadcaster, + router: &router, + message_router: &router, + logger: Arc::clone(&logger), + config: build_node_config(chan_type), + channel_monitors: monitor_refs, + }; + + let manager = <(BlockLocator, ChanMan)>::read(&mut &ser[..], read_args) + .expect("Failed to read manager"); + for (channel_id, mon) in monitors.drain() { + assert_eq!( + chain_monitor.chain_monitor.watch_channel(channel_id, mon), + Ok(ChannelMonitorUpdateStatus::Completed) + ); + } + *chain_monitor.persister.update_ret.lock().unwrap() = *mon_style[node_id as usize].borrow(); + (manager.1, chain_monitor, logger) + }; + let mut read_pos = 1; // First byte was consumed for initial config (mon_style + chan_type) macro_rules! get_slice { ($len: expr) => {{ @@ -1531,82 +1560,6 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let splice_channel = - |node: &ChanMan, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId, - f: &dyn Fn(FundingTemplate) -> Result| { - match node.splice_channel(channel_id, counterparty_node_id) { - Ok(funding_template) => { - if let Ok(contribution) = f(funding_template) { - let _ = node.funding_contributed( - channel_id, - counterparty_node_id, - contribution, - None, - ); - } - }, - Err(e) => { - assert!( - matches!(e, APIError::APIMisuseError { ref err } if err.contains("splice")), - "{:?}", - e - ); - }, - } - }; - - let splice_in = - |node: &ChanMan, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId, - wallet: &WalletSync<&TestWalletSource, Arc>, - funding_feerate_sat_per_kw: FeeRate| { - splice_channel( - node, - counterparty_node_id, - channel_id, - &move |funding_template: FundingTemplate| { - let feerate = - funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); - funding_template.splice_in_sync( - Amount::from_sat(10_000), - feerate, - FeeRate::MAX, - wallet, - ) - }, - ); - }; - - let splice_out = |node: &ChanMan, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId, - wallet: &TestWalletSource, - funding_feerate_sat_per_kw: FeeRate| { - // We conditionally splice out `MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS` only when the node - // has double the balance required to send a payment upon a `0xff` byte. We do this to - // ensure there's always liquidity available for a payment to succeed then. - let outbound_capacity_msat = node - .list_channels() - .iter() - .find(|chan| chan.channel_id == *channel_id) - .map(|chan| chan.outbound_capacity_msat) - .unwrap(); - if outbound_capacity_msat < 20_000_000 { - return; - } - splice_channel(node, counterparty_node_id, channel_id, &move |funding_template| { - let feerate = funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); - let outputs = vec![TxOut { - value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS), - script_pubkey: wallet.get_change_script().unwrap(), - }]; - funding_template.splice_out(outputs, feerate, FeeRate::MAX) - }); - }; - loop { // Push any events from Node B onto ba_events and bc_events macro_rules! push_excess_b_events { @@ -2086,7 +2039,8 @@ pub fn do_test(data: &[u8], out: Out) { unsigned_transaction, .. } => { - let signed_tx = wallets[$node].sign_tx(unsigned_transaction).unwrap(); + let signed_tx = + nodes[$node].wallet.sign_tx(unsigned_transaction).unwrap(); nodes[$node] .funding_transaction_signed( &channel_id, @@ -2096,12 +2050,7 @@ pub fn do_test(data: &[u8], out: Out) { .unwrap(); }, events::Event::SplicePending { new_funding_txo, .. } => { - let broadcaster = match $node { - 0 => &broadcast_a, - 1 => &broadcast_b, - _ => &broadcast_c, - }; - let mut txs = broadcaster.txn_broadcasted.borrow_mut(); + let mut txs = nodes[$node].broadcaster.txn_broadcasted.borrow_mut(); assert!(txs.len() >= 1); let splice_tx = txs.remove(0); assert_eq!(new_funding_txo.txid, splice_tx.compute_txid()); @@ -2151,7 +2100,6 @@ pub fn do_test(data: &[u8], out: Out) { } } }; - let complete_all_monitor_updates = |monitor: &Arc, chan_id| { if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { assert!( @@ -2168,6 +2116,85 @@ pub fn do_test(data: &[u8], out: Out) { } }; + let splice_channel = + |node: &HarnessNode<'_>, + counterparty_node_id: &PublicKey, + channel_id: &ChannelId, + f: &dyn Fn( + FundingTemplate, + ) -> Result| { + match node.splice_channel(channel_id, counterparty_node_id) { + Ok(funding_template) => { + if let Ok(contribution) = f(funding_template) { + let _ = node.funding_contributed( + channel_id, + counterparty_node_id, + contribution, + None, + ); + } + }, + Err(e) => { + assert!( + matches!(e, APIError::APIMisuseError { ref err } if err.contains("splice")), + "{:?}", + e + ); + }, + } + }; + + let splice_in = |node: &HarnessNode<'_>, + counterparty_node_id: &PublicKey, + channel_id: &ChannelId| { + let wallet = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); + let funding_feerate_sat_per_kw = node.fee_estimator.feerate_sat_per_kw(); + splice_channel( + node, + counterparty_node_id, + channel_id, + &move |funding_template: FundingTemplate| { + let feerate = + funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); + funding_template.splice_in_sync( + Amount::from_sat(10_000), + feerate, + FeeRate::MAX, + &wallet, + ) + }, + ); + }; + + let splice_out = |node: &HarnessNode<'_>, + counterparty_node_id: &PublicKey, + channel_id: &ChannelId| { + let outbound_capacity_msat = node + .list_channels() + .iter() + .find(|chan| chan.channel_id == *channel_id) + .map(|chan| chan.outbound_capacity_msat) + .unwrap(); + if outbound_capacity_msat < 20_000_000 { + return; + } + let funding_feerate_sat_per_kw = node.fee_estimator.feerate_sat_per_kw(); + splice_channel( + node, + counterparty_node_id, + channel_id, + &move |funding_template: FundingTemplate| { + let feerate = + funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); + let outputs = vec![TxOut { + value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS), + script_pubkey: node.wallet.get_change_script().unwrap(), + }]; + funding_template.splice_out(outputs, feerate, FeeRate::MAX) + }, + ); + }; + let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { let source = &nodes[source_idx]; @@ -2464,43 +2491,47 @@ pub fn do_test(data: &[u8], out: Out) { if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } - if fee_est_a.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate { - fee_est_a.ret_val.store(max_feerate, atomic::Ordering::Release); + if nodes[0].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 + > max_feerate + { + nodes[0].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); } nodes[0].timer_tick_occurred(); }, 0x81 => { - fee_est_a.ret_val.store(253, atomic::Ordering::Release); + nodes[0].fee_estimator.ret_val.store(253, atomic::Ordering::Release); nodes[0].timer_tick_occurred(); }, - 0x84 => { let mut max_feerate = last_htlc_clear_fee_b; if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } - if fee_est_b.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate { - fee_est_b.ret_val.store(max_feerate, atomic::Ordering::Release); + if nodes[1].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 + > max_feerate + { + nodes[1].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); } nodes[1].timer_tick_occurred(); }, 0x85 => { - fee_est_b.ret_val.store(253, atomic::Ordering::Release); + nodes[1].fee_estimator.ret_val.store(253, atomic::Ordering::Release); nodes[1].timer_tick_occurred(); }, - 0x88 => { let mut max_feerate = last_htlc_clear_fee_c; if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } - if fee_est_c.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate { - fee_est_c.ret_val.store(max_feerate, atomic::Ordering::Release); + if nodes[2].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 + > max_feerate + { + nodes[2].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); } nodes[2].timer_tick_occurred(); }, 0x89 => { - fee_est_c.ret_val.store(253, atomic::Ordering::Release); + nodes[2].fee_estimator.ret_val.store(253, atomic::Ordering::Release); nodes[2].timer_tick_occurred(); }, @@ -2509,36 +2540,28 @@ pub fn do_test(data: &[u8], out: Out) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - let wallet = WalletSync::new(&wallets[0], Arc::clone(&loggers[0])); - let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw(); - splice_in(&nodes[0], &cp_node_id, &chan_a_id, &wallet, feerate_sat_per_kw); + splice_in(&nodes[0], &cp_node_id, &chan_a_id); }, 0xa1 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[0].get_our_node_id(); - let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1])); - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_in(&nodes[1], &cp_node_id, &chan_a_id, &wallet, feerate_sat_per_kw); + splice_in(&nodes[1], &cp_node_id, &chan_a_id); }, 0xa2 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[2].get_our_node_id(); - let wallet = WalletSync::new(&wallets[1], Arc::clone(&loggers[1])); - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_in(&nodes[1], &cp_node_id, &chan_b_id, &wallet, feerate_sat_per_kw); + splice_in(&nodes[1], &cp_node_id, &chan_b_id); }, 0xa3 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - let wallet = WalletSync::new(&wallets[2], Arc::clone(&loggers[2])); - let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw(); - splice_in(&nodes[2], &cp_node_id, &chan_b_id, &wallet, feerate_sat_per_kw); + splice_in(&nodes[2], &cp_node_id, &chan_b_id); }, 0xa4 => { @@ -2546,63 +2569,55 @@ pub fn do_test(data: &[u8], out: Out) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - let wallet = &wallets[0]; - let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw(); - splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw); + splice_out(&nodes[0], &cp_node_id, &chan_a_id); }, 0xa5 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[0].get_our_node_id(); - let wallet = &wallets[1]; - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw); + splice_out(&nodes[1], &cp_node_id, &chan_a_id); }, 0xa6 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[2].get_our_node_id(); - let wallet = &wallets[1]; - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw); + splice_out(&nodes[1], &cp_node_id, &chan_b_id); }, 0xa7 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - let wallet = &wallets[2]; - let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw(); - splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw); + splice_out(&nodes[2], &cp_node_id, &chan_b_id); }, // Sync node by 1 block to cover confirmation of a transaction. 0xa8 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, Some(1)); + sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, Some(1)); }, 0xa9 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, Some(1)); + sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, Some(1)); }, 0xaa => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, Some(1)); + sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, Some(1)); }, // Sync node to chain tip to cover confirmation of a transaction post-reorg-risk. 0xab => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, None); + sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None); }, 0xac => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None); + sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None); }, 0xad => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None); + sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None); }, 0xb0 | 0xb1 | 0xb2 => { @@ -2618,18 +2633,19 @@ pub fn do_test(data: &[u8], out: Out) { ab_events.clear(); ba_events.clear(); } - let (new_node_a, new_monitor_a) = reload_node( + let (new_node_a, new_monitor_a, new_logger_a) = reload_node( &node_a_ser, 0, &monitor_a, v, &keys_manager_a, &fee_est_a, - broadcast_a.clone(), + Arc::clone(&broadcast_a), ); nodes[0].node = new_node_a; monitor_a = Arc::clone(&new_monitor_a); nodes[0].monitor = new_monitor_a; + nodes[0].logger = new_logger_a; }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on @@ -2648,18 +2664,19 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_b, new_monitor_b) = reload_node( + let (new_node_b, new_monitor_b, new_logger_b) = reload_node( &node_b_ser, 1, &monitor_b, v, &keys_manager_b, &fee_est_b, - broadcast_b.clone(), + Arc::clone(&broadcast_b), ); nodes[1].node = new_node_b; monitor_b = Arc::clone(&new_monitor_b); nodes[1].monitor = new_monitor_b; + nodes[1].logger = new_logger_b; }, 0xbc | 0xbd | 0xbe => { // Restart node C, picking among the in-flight `ChannelMonitor`s to use based on @@ -2674,18 +2691,19 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_c, new_monitor_c) = reload_node( + let (new_node_c, new_monitor_c, new_logger_c) = reload_node( &node_c_ser, 2, &monitor_c, v, &keys_manager_c, &fee_est_c, - broadcast_c.clone(), + Arc::clone(&broadcast_c), ); nodes[2].node = new_node_c; monitor_c = Arc::clone(&new_monitor_c); nodes[2].monitor = new_monitor_c; + nodes[2].logger = new_logger_c; }, 0xc0 => keys_manager_a.disable_supported_ops_for_all_signers(), @@ -2960,20 +2978,23 @@ pub fn do_test(data: &[u8], out: Out) { ); } - 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); - last_htlc_clear_fee_c = fee_est_c.ret_val.load(atomic::Ordering::Acquire); + last_htlc_clear_fee_a = + nodes[0].fee_estimator.ret_val.load(atomic::Ordering::Acquire); + last_htlc_clear_fee_b = + nodes[1].fee_estimator.ret_val.load(atomic::Ordering::Acquire); + last_htlc_clear_fee_c = + nodes[2].fee_estimator.ret_val.load(atomic::Ordering::Acquire); }, _ => test_return!(), } - if nodes[0].get_and_clear_needs_persistence() == true { + if nodes[0].get_and_clear_needs_persistence() { node_a_ser = nodes[0].encode(); } - if nodes[1].get_and_clear_needs_persistence() == true { + if nodes[1].get_and_clear_needs_persistence() { node_b_ser = nodes[1].encode(); } - if nodes[2].get_and_clear_needs_persistence() == true { + if nodes[2].get_and_clear_needs_persistence() { node_c_ser = nodes[2].encode(); } } From 7b4cf595df82a941803d6d3ac1999846c491eb40 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 18:00:24 +0200 Subject: [PATCH 04/11] Extract chanmon harness nodes Centralize creation of the three chanmon harness nodes. The fuzzer now initializes the node array through one path, which reduces duplicated setup before the event and payment helpers are split out. --- fuzz/src/chanmon_consistency.rs | 155 +++++++++++++------------------- 1 file changed, 63 insertions(+), 92 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 2e842b2a44e..59a94ade5db 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1366,12 +1366,6 @@ pub fn do_test(data: &[u8], out: Out) { chan_type, ), ]; - let mut monitor_a = Arc::clone(&nodes[0].monitor); - let mut monitor_b = Arc::clone(&nodes[1].monitor); - let mut monitor_c = Arc::clone(&nodes[2].monitor); - let keys_manager_a = Arc::clone(&nodes[0].keys_manager); - let keys_manager_b = Arc::clone(&nodes[1].keys_manager); - let keys_manager_c = Arc::clone(&nodes[2].keys_manager); // Connect peers first, then create channels connect_peers(&nodes[0], &nodes[1]); @@ -1466,25 +1460,18 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let reload_node = |ser: &Vec, - node_id: u8, - old_monitors: &TestChainMonitor, - mut use_old_mons, - keys: &Arc, - fee_estimator: &Arc, - broadcaster: Arc| { - let keys_manager = Arc::clone(keys); + let reload_node = |ser: &Vec, node_id: u8, old_node: &HarnessNode<'_>, mut use_old_mons| { let (logger_for_monitor, logger) = HarnessNode::build_loggers(node_id, &out); let chain_monitor = HarnessNode::build_chain_monitor( - &broadcaster, - fee_estimator, - &keys_manager, + &old_node.broadcaster, + &old_node.fee_estimator, + &old_node.keys_manager, logger_for_monitor, ChannelMonitorUpdateStatus::Completed, ); let mut monitors = new_hash_map(); - let mut old_monitors = old_monitors.latest_monitors.lock().unwrap(); + let mut old_monitors = old_node.monitor.latest_monitors.lock().unwrap(); for (channel_id, mut prev_state) in old_monitors.drain() { let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { // Reload with the oldest `ChannelMonitor` (the one that we already told @@ -1504,7 +1491,7 @@ pub fn do_test(data: &[u8], out: Out) { use_old_mons /= 3; let mon = <(BlockLocator, ChannelMonitor)>::read( &mut &serialized_mon[..], - (&*keys_manager, &*keys_manager), + (&*old_node.keys_manager, &*old_node.keys_manager), ) .expect("Failed to read monitor"); monitors.insert(channel_id, mon.1); @@ -1523,12 +1510,12 @@ pub fn do_test(data: &[u8], out: Out) { } let read_args = ChannelManagerReadArgs { - entropy_source: Arc::clone(&keys_manager), - node_signer: Arc::clone(&keys_manager), - signer_provider: Arc::clone(&keys_manager), - fee_estimator: Arc::clone(fee_estimator), + entropy_source: Arc::clone(&old_node.keys_manager), + node_signer: Arc::clone(&old_node.keys_manager), + signer_provider: Arc::clone(&old_node.keys_manager), + fee_estimator: Arc::clone(&old_node.fee_estimator), chain_monitor: chain_monitor.clone(), - tx_broadcaster: broadcaster, + tx_broadcaster: Arc::clone(&old_node.broadcaster), router: &router, message_router: &router, logger: Arc::clone(&logger), @@ -2315,22 +2302,22 @@ pub fn do_test(data: &[u8], out: Out) { 0x08 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_a, id); + complete_all_monitor_updates(&nodes[0].monitor, id); } }, 0x09 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&nodes[1].monitor, id); } }, 0x0a => { for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&nodes[1].monitor, id); } }, 0x0b => { for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_c, id); + complete_all_monitor_updates(&nodes[2].monitor, id); } }, @@ -2633,17 +2620,9 @@ pub fn do_test(data: &[u8], out: Out) { ab_events.clear(); ba_events.clear(); } - let (new_node_a, new_monitor_a, new_logger_a) = reload_node( - &node_a_ser, - 0, - &monitor_a, - v, - &keys_manager_a, - &fee_est_a, - Arc::clone(&broadcast_a), - ); + let (new_node_a, new_monitor_a, new_logger_a) = + reload_node(&node_a_ser, 0, &nodes[0], v); nodes[0].node = new_node_a; - monitor_a = Arc::clone(&new_monitor_a); nodes[0].monitor = new_monitor_a; nodes[0].logger = new_logger_a; }, @@ -2664,17 +2643,9 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_b, new_monitor_b, new_logger_b) = reload_node( - &node_b_ser, - 1, - &monitor_b, - v, - &keys_manager_b, - &fee_est_b, - Arc::clone(&broadcast_b), - ); + let (new_node_b, new_monitor_b, new_logger_b) = + reload_node(&node_b_ser, 1, &nodes[1], v); nodes[1].node = new_node_b; - monitor_b = Arc::clone(&new_monitor_b); nodes[1].monitor = new_monitor_b; nodes[1].logger = new_logger_b; }, @@ -2691,140 +2662,140 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_c, new_monitor_c, new_logger_c) = reload_node( - &node_c_ser, - 2, - &monitor_c, - v, - &keys_manager_c, - &fee_est_c, - Arc::clone(&broadcast_c), - ); + let (new_node_c, new_monitor_c, new_logger_c) = + reload_node(&node_c_ser, 2, &nodes[2], v); nodes[2].node = new_node_c; - monitor_c = Arc::clone(&new_monitor_c); nodes[2].monitor = new_monitor_c; nodes[2].logger = new_logger_c; }, - 0xc0 => keys_manager_a.disable_supported_ops_for_all_signers(), - 0xc1 => keys_manager_b.disable_supported_ops_for_all_signers(), - 0xc2 => keys_manager_c.disable_supported_ops_for_all_signers(), + 0xc0 => nodes[0].keys_manager.disable_supported_ops_for_all_signers(), + 0xc1 => nodes[1].keys_manager.disable_supported_ops_for_all_signers(), + 0xc2 => nodes[2].keys_manager.disable_supported_ops_for_all_signers(), 0xc3 => { - keys_manager_a.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); + nodes[0] + .keys_manager + .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); nodes[0].signer_unblocked(None); }, 0xc4 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); + nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); nodes[1].signer_unblocked(filter); }, 0xc5 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); + nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); nodes[1].signer_unblocked(filter); }, 0xc6 => { - keys_manager_c.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); + nodes[2] + .keys_manager + .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); nodes[2].signer_unblocked(None); }, 0xc7 => { - keys_manager_a.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); nodes[0].signer_unblocked(None); }, 0xc8 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); nodes[1].signer_unblocked(filter); }, 0xc9 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); nodes[1].signer_unblocked(filter); }, 0xca => { - keys_manager_c.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); nodes[2].signer_unblocked(None); }, 0xcb => { - keys_manager_a.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); nodes[0].signer_unblocked(None); }, 0xcc => { - keys_manager_b.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); nodes[1].signer_unblocked(filter); }, 0xcd => { - keys_manager_b.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); nodes[1].signer_unblocked(filter); }, 0xce => { - keys_manager_c.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); nodes[2].signer_unblocked(None); }, 0xf0 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &complete_first); + complete_monitor_update(&nodes[0].monitor, id, &complete_first); } }, 0xf1 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &complete_second); + complete_monitor_update(&nodes[0].monitor, id, &complete_second); } }, 0xf2 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &Vec::pop); + complete_monitor_update(&nodes[0].monitor, id, &Vec::pop); } }, 0xf4 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &complete_first); + complete_monitor_update(&nodes[1].monitor, id, &complete_first); } }, 0xf5 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &complete_second); + complete_monitor_update(&nodes[1].monitor, id, &complete_second); } }, 0xf6 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &Vec::pop); + complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); } }, 0xf8 => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &complete_first); + complete_monitor_update(&nodes[1].monitor, id, &complete_first); } }, 0xf9 => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &complete_second); + complete_monitor_update(&nodes[1].monitor, id, &complete_second); } }, 0xfa => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &Vec::pop); + complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); } }, 0xfc => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &complete_first); + complete_monitor_update(&nodes[2].monitor, id, &complete_first); } }, 0xfd => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &complete_second); + complete_monitor_update(&nodes[2].monitor, id, &complete_second); } }, 0xfe => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &Vec::pop); + complete_monitor_update(&nodes[2].monitor, id, &Vec::pop); } }, @@ -2865,9 +2836,9 @@ pub fn do_test(data: &[u8], out: Out) { } for op in SUPPORTED_SIGNER_OPS { - keys_manager_a.enable_op_for_all_signers(op); - keys_manager_b.enable_op_for_all_signers(op); - keys_manager_c.enable_op_for_all_signers(op); + nodes[0].keys_manager.enable_op_for_all_signers(op); + nodes[1].keys_manager.enable_op_for_all_signers(op); + nodes[2].keys_manager.enable_op_for_all_signers(op); } nodes[0].signer_unblocked(None); nodes[1].signer_unblocked(None); @@ -2882,12 +2853,12 @@ pub fn do_test(data: &[u8], out: Out) { } // Next, make sure no monitor updates are pending for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_a, id); - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&nodes[0].monitor, id); + complete_all_monitor_updates(&nodes[1].monitor, id); } for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_b, id); - complete_all_monitor_updates(&monitor_c, id); + complete_all_monitor_updates(&nodes[1].monitor, id); + complete_all_monitor_updates(&nodes[2].monitor, id); } // Then, make sure any current forwards make their way to their destination if process_msg_events!(0, false, ProcessMessages::AllMessages) { From 51f6fd5666ad75931dbea2f0ec8646263bb1b069 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:24:01 +0200 Subject: [PATCH 05/11] Extract chanmon harness node lifecycle Move persistence, reload, and chain sync state onto each harness node. Keeping serialized managers and heights with the node makes restarts and block updates easier to reason about. --- fuzz/src/chanmon_consistency.rs | 337 ++++++++++++++++---------------- 1 file changed, 167 insertions(+), 170 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 59a94ade5db..84a5332718c 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -944,6 +944,7 @@ enum ChanType { } struct HarnessNode<'a> { + node_id: u8, node: ChanMan<'a>, monitor: Arc, keys_manager: Arc, @@ -951,6 +952,10 @@ struct HarnessNode<'a> { broadcaster: Arc, fee_estimator: Arc, wallet: TestWalletSource, + persistence_style: ChannelMonitorUpdateStatus, + serialized_manager: Vec, + height: u32, + last_htlc_clear_fee: u32, } impl<'a> std::ops::Deref for HarnessNode<'a> { @@ -1025,7 +1030,24 @@ impl<'a> HarnessNode<'a> { params, best_block_timestamp, ); - Self { node, monitor, keys_manager, logger, broadcaster, fee_estimator, wallet } + Self { + node_id, + node, + monitor, + keys_manager, + logger, + broadcaster, + fee_estimator, + wallet, + persistence_style, + serialized_manager: Vec::new(), + height: 0, + last_htlc_clear_fee: 253, + } + } + + fn set_persistence_style(&mut self, style: ChannelMonitorUpdateStatus) { + self.persistence_style = style; } fn complete_all_pending_monitor_updates(&self) { @@ -1039,6 +1061,94 @@ impl<'a> HarnessNode<'a> { } } } + + fn refresh_serialized_manager(&mut self) { + if self.node.get_and_clear_needs_persistence() { + self.serialized_manager = self.node.encode(); + } + } + + fn reload( + &mut self, use_old_mons: u8, out: &Out, router: &'a FuzzRouter, chan_type: ChanType, + ) { + let (logger_for_monitor, logger) = Self::build_loggers(self.node_id, out); + let chain_monitor = Self::build_chain_monitor( + &self.broadcaster, + &self.fee_estimator, + &self.keys_manager, + logger_for_monitor, + ChannelMonitorUpdateStatus::Completed, + ); + + let mut monitors = new_hash_map(); + let mut use_old_mons = use_old_mons; + { + let mut old_monitors = self.monitor.latest_monitors.lock().unwrap(); + for (channel_id, mut prev_state) in old_monitors.drain() { + let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { + // Reload with the oldest `ChannelMonitor` (the one that we already told + // `ChannelManager` we finished persisting). + (prev_state.persisted_monitor_id, prev_state.persisted_monitor) + } else if use_old_mons % 3 == 1 { + // Reload with the second-oldest `ChannelMonitor`. + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.drain(..).next().unwrap_or(old_mon) + } else { + // Reload with the newest `ChannelMonitor`. + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.pop().unwrap_or(old_mon) + }; + // Use a different value of `use_old_mons` if we have another monitor + // (only for node B) by shifting `use_old_mons` one in base-3. + use_old_mons /= 3; + let mon = <(BlockLocator, ChannelMonitor)>::read( + &mut &serialized_mon[..], + (&*self.keys_manager, &*self.keys_manager), + ) + .expect("Failed to read monitor"); + monitors.insert(channel_id, mon.1); + // Update the latest `ChannelMonitor` state to match what we just told LDK. + prev_state.persisted_monitor = serialized_mon; + prev_state.persisted_monitor_id = mon_id; + // Wipe any `ChannelMonitor`s which we never told LDK we finished persisting, + // considering them discarded. LDK should replay these for us as they're stored in + // the `ChannelManager`. + prev_state.pending_monitors.clear(); + chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); + } + } + let mut monitor_refs = new_hash_map(); + for (channel_id, monitor) in monitors.iter() { + monitor_refs.insert(*channel_id, monitor); + } + + let read_args = ChannelManagerReadArgs { + entropy_source: Arc::clone(&self.keys_manager), + node_signer: Arc::clone(&self.keys_manager), + signer_provider: Arc::clone(&self.keys_manager), + fee_estimator: Arc::clone(&self.fee_estimator), + chain_monitor: Arc::clone(&chain_monitor), + tx_broadcaster: Arc::clone(&self.broadcaster), + router, + message_router: router, + logger: Arc::clone(&logger), + config: build_node_config(chan_type), + channel_monitors: monitor_refs, + }; + + let manager = <(BlockLocator, ChanMan)>::read(&mut &self.serialized_manager[..], read_args) + .expect("Failed to read manager"); + for (channel_id, mon) in monitors.drain() { + assert_eq!( + chain_monitor.chain_monitor.watch_channel(channel_id, mon), + Ok(ChannelMonitorUpdateStatus::Completed) + ); + } + *chain_monitor.persister.update_ret.lock().unwrap() = self.persistence_style; + self.node = manager.1; + self.monitor = chain_monitor; + self.logger = logger; + } } fn build_node_config(chan_type: ChanType) -> UserConfig { @@ -1279,28 +1389,25 @@ pub fn do_test(data: &[u8], out: Out) { 1 => ChanType::KeyedAnchors, _ => ChanType::ZeroFeeCommitments, }; - let mon_style = [ - RefCell::new(if config_byte & 0b01 != 0 { + let persistence_styles = [ + if config_byte & 0b01 != 0 { ChannelMonitorUpdateStatus::InProgress } else { ChannelMonitorUpdateStatus::Completed - }), - RefCell::new(if config_byte & 0b10 != 0 { + }, + if config_byte & 0b10 != 0 { ChannelMonitorUpdateStatus::InProgress } else { ChannelMonitorUpdateStatus::Completed - }), - RefCell::new(if config_byte & 0b100 != 0 { + }, + if config_byte & 0b100 != 0 { ChannelMonitorUpdateStatus::InProgress } else { ChannelMonitorUpdateStatus::Completed - }), + }, ]; let mut chain_state = ChainState::new(); - let mut node_height_a: u32 = 0; - let mut node_height_b: u32 = 0; - let mut node_height_c: u32 = 0; let wallet_a = TestWalletSource::new(SecretKey::from_slice(&[1; 32]).unwrap()); let wallet_b = TestWalletSource::new(SecretKey::from_slice(&[2; 32]).unwrap()); let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); @@ -1323,11 +1430,8 @@ pub fn do_test(data: &[u8], out: Out) { } let fee_est_a = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let mut last_htlc_clear_fee_a = 253; let fee_est_b = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let mut last_htlc_clear_fee_b = 253; let fee_est_c = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let mut last_htlc_clear_fee_c = 253; let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); @@ -1340,7 +1444,7 @@ pub fn do_test(data: &[u8], out: Out) { wallet_a, Arc::clone(&fee_est_a), Arc::clone(&broadcast_a), - mon_style[0].borrow().clone(), + persistence_styles[0], &out, &router, chan_type, @@ -1350,7 +1454,7 @@ pub fn do_test(data: &[u8], out: Out) { wallet_b, Arc::clone(&fee_est_b), Arc::clone(&broadcast_b), - mon_style[1].borrow().clone(), + persistence_styles[1], &out, &router, chan_type, @@ -1360,7 +1464,7 @@ pub fn do_test(data: &[u8], out: Out) { wallet_c, Arc::clone(&fee_est_c), Arc::clone(&broadcast_c), - mon_style[2].borrow().clone(), + persistence_styles[2], &out, &router, chan_type, @@ -1392,30 +1496,28 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); - let sync_with_chain_state = |chain_state: &ChainState, - node: &HarnessNode<'_>, - node_height: &mut u32, - num_blocks: Option| { - let target_height = if let Some(num_blocks) = num_blocks { - std::cmp::min(*node_height + num_blocks, chain_state.tip_height()) - } else { - chain_state.tip_height() - }; - while *node_height < target_height { - *node_height += 1; - let (header, txn) = chain_state.block_at(*node_height); - let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); - if !txdata.is_empty() { - node.transactions_confirmed(header, &txdata, *node_height); + let sync_with_chain_state = + |node: &mut HarnessNode<'_>, chain_state: &ChainState, num_blocks: Option| { + let target_height = if let Some(num_blocks) = num_blocks { + std::cmp::min(node.height + num_blocks, chain_state.tip_height()) + } else { + chain_state.tip_height() + }; + while node.height < target_height { + node.height += 1; + let (header, txn) = chain_state.block_at(node.height); + let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); + if !txdata.is_empty() { + node.transactions_confirmed(header, &txdata, node.height); + } + node.best_block_updated(header, node.height); } - node.best_block_updated(header, *node_height); - } - }; + }; // Sync all nodes to tip to lock the funding. - sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None); - sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None); - sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None); + sync_with_chain_state(&mut nodes[0], &chain_state, None); + sync_with_chain_state(&mut nodes[1], &chain_state, None); + sync_with_chain_state(&mut nodes[2], &chain_state, None); lock_fundings(&nodes); @@ -1442,9 +1544,9 @@ pub fn do_test(data: &[u8], out: Out) { let mut bc_events = Vec::new(); let mut cb_events = Vec::new(); - let mut node_a_ser = nodes[0].encode(); - let mut node_b_ser = nodes[1].encode(); - let mut node_c_ser = nodes[2].encode(); + for node in &mut nodes { + node.serialized_manager = node.encode(); + } let pending_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]); let resolved_payments: RefCell<[HashMap>; 3]> = @@ -1460,82 +1562,7 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let reload_node = |ser: &Vec, node_id: u8, old_node: &HarnessNode<'_>, mut use_old_mons| { - let (logger_for_monitor, logger) = HarnessNode::build_loggers(node_id, &out); - let chain_monitor = HarnessNode::build_chain_monitor( - &old_node.broadcaster, - &old_node.fee_estimator, - &old_node.keys_manager, - logger_for_monitor, - ChannelMonitorUpdateStatus::Completed, - ); - - let mut monitors = new_hash_map(); - let mut old_monitors = old_node.monitor.latest_monitors.lock().unwrap(); - for (channel_id, mut prev_state) in old_monitors.drain() { - let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { - // Reload with the oldest `ChannelMonitor` (the one that we already told - // `ChannelManager` we finished persisting). - (prev_state.persisted_monitor_id, prev_state.persisted_monitor) - } else if use_old_mons % 3 == 1 { - // Reload with the second-oldest `ChannelMonitor` - let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); - prev_state.pending_monitors.drain(..).next().unwrap_or(old_mon) - } else { - // Reload with the newest `ChannelMonitor` - let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); - prev_state.pending_monitors.pop().unwrap_or(old_mon) - }; - // Use a different value of `use_old_mons` if we have another monitor (only for node B) - // by shifting `use_old_mons` one in base-3. - use_old_mons /= 3; - let mon = <(BlockLocator, ChannelMonitor)>::read( - &mut &serialized_mon[..], - (&*old_node.keys_manager, &*old_node.keys_manager), - ) - .expect("Failed to read monitor"); - monitors.insert(channel_id, mon.1); - // Update the latest `ChannelMonitor` state to match what we just told LDK. - prev_state.persisted_monitor = serialized_mon; - prev_state.persisted_monitor_id = mon_id; - // Wipe any `ChannelMonitor`s which we never told LDK we finished persisting, - // considering them discarded. LDK should replay these for us as they're stored in - // the `ChannelManager`. - prev_state.pending_monitors.clear(); - chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); - } - let mut monitor_refs = new_hash_map(); - for (channel_id, monitor) in monitors.iter() { - monitor_refs.insert(*channel_id, monitor); - } - - let read_args = ChannelManagerReadArgs { - entropy_source: Arc::clone(&old_node.keys_manager), - node_signer: Arc::clone(&old_node.keys_manager), - signer_provider: Arc::clone(&old_node.keys_manager), - fee_estimator: Arc::clone(&old_node.fee_estimator), - chain_monitor: chain_monitor.clone(), - tx_broadcaster: Arc::clone(&old_node.broadcaster), - router: &router, - message_router: &router, - logger: Arc::clone(&logger), - config: build_node_config(chan_type), - channel_monitors: monitor_refs, - }; - - let manager = <(BlockLocator, ChanMan)>::read(&mut &ser[..], read_args) - .expect("Failed to read manager"); - for (channel_id, mon) in monitors.drain() { - assert_eq!( - chain_monitor.chain_monitor.watch_channel(channel_id, mon), - Ok(ChannelMonitorUpdateStatus::Completed) - ); - } - *chain_monitor.persister.update_ret.lock().unwrap() = *mon_style[node_id as usize].borrow(); - (manager.1, chain_monitor, logger) - }; - - let mut read_pos = 1; // First byte was consumed for initial config (mon_style + chan_type) + let mut read_pos = 1; // First byte was consumed for initial config (persistence styles + chan_type) macro_rules! get_slice { ($len: expr) => {{ let slice_len = $len as usize; @@ -2281,24 +2308,12 @@ pub fn do_test(data: &[u8], out: Out) { // In general, we keep related message groups close together in binary form, allowing // bit-twiddling mutations to have similar effects. This is probably overkill, but no // harm in doing so. - 0x00 => { - *mon_style[0].borrow_mut() = ChannelMonitorUpdateStatus::InProgress; - }, - 0x01 => { - *mon_style[1].borrow_mut() = ChannelMonitorUpdateStatus::InProgress; - }, - 0x02 => { - *mon_style[2].borrow_mut() = ChannelMonitorUpdateStatus::InProgress; - }, - 0x04 => { - *mon_style[0].borrow_mut() = ChannelMonitorUpdateStatus::Completed; - }, - 0x05 => { - *mon_style[1].borrow_mut() = ChannelMonitorUpdateStatus::Completed; - }, - 0x06 => { - *mon_style[2].borrow_mut() = ChannelMonitorUpdateStatus::Completed; - }, + 0x00 => nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x01 => nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x02 => nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x04 => nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::Completed), + 0x05 => nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::Completed), + 0x06 => nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::Completed), 0x08 => { for id in &chan_ab_ids { @@ -2474,7 +2489,7 @@ pub fn do_test(data: &[u8], out: Out) { }, 0x80 => { - let mut max_feerate = last_htlc_clear_fee_a; + let mut max_feerate = nodes[0].last_htlc_clear_fee; if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } @@ -2490,7 +2505,7 @@ pub fn do_test(data: &[u8], out: Out) { nodes[0].timer_tick_occurred(); }, 0x84 => { - let mut max_feerate = last_htlc_clear_fee_b; + let mut max_feerate = nodes[1].last_htlc_clear_fee; if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } @@ -2506,7 +2521,7 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].timer_tick_occurred(); }, 0x88 => { - let mut max_feerate = last_htlc_clear_fee_c; + let mut max_feerate = nodes[2].last_htlc_clear_fee; if matches!(chan_type, ChanType::Legacy) { max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; } @@ -2583,28 +2598,28 @@ pub fn do_test(data: &[u8], out: Out) { // Sync node by 1 block to cover confirmation of a transaction. 0xa8 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, Some(1)); + sync_with_chain_state(&mut nodes[0], &chain_state, Some(1)); }, 0xa9 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, Some(1)); + sync_with_chain_state(&mut nodes[1], &chain_state, Some(1)); }, 0xaa => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, Some(1)); + sync_with_chain_state(&mut nodes[2], &chain_state, Some(1)); }, // Sync node to chain tip to cover confirmation of a transaction post-reorg-risk. 0xab => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None); + sync_with_chain_state(&mut nodes[0], &chain_state, None); }, 0xac => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None); + sync_with_chain_state(&mut nodes[1], &chain_state, None); }, 0xad => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None); + sync_with_chain_state(&mut nodes[2], &chain_state, None); }, 0xb0 | 0xb1 | 0xb2 => { @@ -2620,11 +2635,7 @@ pub fn do_test(data: &[u8], out: Out) { ab_events.clear(); ba_events.clear(); } - let (new_node_a, new_monitor_a, new_logger_a) = - reload_node(&node_a_ser, 0, &nodes[0], v); - nodes[0].node = new_node_a; - nodes[0].monitor = new_monitor_a; - nodes[0].logger = new_logger_a; + nodes[0].reload(v, &out, &router, chan_type); }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on @@ -2643,11 +2654,7 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_b, new_monitor_b, new_logger_b) = - reload_node(&node_b_ser, 1, &nodes[1], v); - nodes[1].node = new_node_b; - nodes[1].monitor = new_monitor_b; - nodes[1].logger = new_logger_b; + nodes[1].reload(v, &out, &router, chan_type); }, 0xbc | 0xbd | 0xbe => { // Restart node C, picking among the in-flight `ChannelMonitor`s to use based on @@ -2662,11 +2669,7 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_c, new_monitor_c, new_logger_c) = - reload_node(&node_c_ser, 2, &nodes[2], v); - nodes[2].node = new_node_c; - nodes[2].monitor = new_monitor_c; - nodes[2].logger = new_logger_c; + nodes[2].reload(v, &out, &router, chan_type); }, 0xc0 => nodes[0].keys_manager.disable_supported_ops_for_all_signers(), @@ -2949,24 +2952,18 @@ pub fn do_test(data: &[u8], out: Out) { ); } - last_htlc_clear_fee_a = + nodes[0].last_htlc_clear_fee = nodes[0].fee_estimator.ret_val.load(atomic::Ordering::Acquire); - last_htlc_clear_fee_b = + nodes[1].last_htlc_clear_fee = nodes[1].fee_estimator.ret_val.load(atomic::Ordering::Acquire); - last_htlc_clear_fee_c = + nodes[2].last_htlc_clear_fee = nodes[2].fee_estimator.ret_val.load(atomic::Ordering::Acquire); }, _ => test_return!(), } - if nodes[0].get_and_clear_needs_persistence() { - node_a_ser = nodes[0].encode(); - } - if nodes[1].get_and_clear_needs_persistence() { - node_b_ser = nodes[1].encode(); - } - if nodes[2].get_and_clear_needs_persistence() { - node_c_ser = nodes[2].encode(); + for node in &mut nodes { + node.refresh_serialized_manager(); } } } From 54fc23de504762684ee11e754f90c284669f15dc Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:32:31 +0200 Subject: [PATCH 06/11] Extract chanmon harness node operations Move the action helpers onto `HarnessNode` methods. Node-local operations now live with the state they mutate, which reduces argument threading through the fuzz loop. --- fuzz/src/chanmon_consistency.rs | 442 ++++++++++++++++---------------- 1 file changed, 217 insertions(+), 225 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 84a5332718c..fb911eb644a 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -56,7 +56,6 @@ use lightning::ln::channelmanager::{ TrustedChannelFeatures, }; use lightning::ln::functional_test_utils::*; -use lightning::ln::funding::{FundingContribution, FundingContributionError, FundingTemplate}; use lightning::ln::inbound_payment::ExpandedKey; use lightning::ln::msgs::{ self, BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, Init, MessageSendEvent, @@ -1050,6 +1049,22 @@ impl<'a> HarnessNode<'a> { self.persistence_style = style; } + fn complete_all_monitor_updates(&self, chan_id: &ChannelId) { + if let Some(state) = self.monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { + assert!( + state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), + "updates should be sorted by id" + ); + for (id, data) in state.pending_monitors.drain(..) { + self.monitor.chain_monitor.channel_monitor_updated(*chan_id, id).unwrap(); + if id > state.persisted_monitor_id { + state.persisted_monitor_id = id; + state.persisted_monitor = data; + } + } + } + } + fn complete_all_pending_monitor_updates(&self) { for (channel_id, state) in self.monitor.latest_monitors.lock().unwrap().iter_mut() { for (id, data) in state.pending_monitors.drain(..) { @@ -1062,12 +1077,160 @@ impl<'a> HarnessNode<'a> { } } + fn complete_monitor_update(&self, chan_id: &ChannelId, selector: MonitorUpdateSelector) { + if let Some(state) = self.monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { + assert!( + state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), + "updates should be sorted by id" + ); + let update = match selector { + MonitorUpdateSelector::First => { + if state.pending_monitors.is_empty() { + None + } else { + Some(state.pending_monitors.remove(0)) + } + }, + MonitorUpdateSelector::Second => { + if state.pending_monitors.len() > 1 { + Some(state.pending_monitors.remove(1)) + } else { + None + } + }, + MonitorUpdateSelector::Last => state.pending_monitors.pop(), + }; + if let Some((id, data)) = update { + self.monitor.chain_monitor.channel_monitor_updated(*chan_id, id).unwrap(); + if id > state.persisted_monitor_id { + state.persisted_monitor_id = id; + state.persisted_monitor = data; + } + } + } + } + + fn sync_with_chain_state(&mut self, chain_state: &ChainState, num_blocks: Option) { + let target_height = if let Some(num_blocks) = num_blocks { + std::cmp::min(self.height + num_blocks, chain_state.tip_height()) + } else { + chain_state.tip_height() + }; + + while self.height < target_height { + self.height += 1; + let (header, txn) = chain_state.block_at(self.height); + let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); + if !txdata.is_empty() { + self.node.transactions_confirmed(header, &txdata, self.height); + } + self.node.best_block_updated(header, self.height); + } + } + fn refresh_serialized_manager(&mut self) { if self.node.get_and_clear_needs_persistence() { self.serialized_manager = self.node.encode(); } } + fn bump_fee_estimate(&mut self, chan_type: ChanType) { + let mut max_feerate = self.last_htlc_clear_fee; + if matches!(chan_type, ChanType::Legacy) { + max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; + } + if self.fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate { + self.fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); + } + self.node.timer_tick_occurred(); + } + + fn reset_fee_estimate(&self) { + self.fee_estimator.ret_val.store(253, atomic::Ordering::Release); + self.node.timer_tick_occurred(); + } + + fn current_feerate_sat_per_kw(&self) -> FeeRate { + self.fee_estimator.feerate_sat_per_kw() + } + + fn record_last_htlc_clear_fee(&mut self) { + self.last_htlc_clear_fee = self.fee_estimator.ret_val.load(atomic::Ordering::Acquire); + } + + fn splice_in(&self, counterparty_node_id: &PublicKey, channel_id: &ChannelId) { + let wallet = WalletSync::new(&self.wallet, Arc::clone(&self.logger)); + match self.node.splice_channel(channel_id, counterparty_node_id) { + Ok(funding_template) => { + let feerate = + funding_template.min_rbf_feerate().unwrap_or(self.current_feerate_sat_per_kw()); + if let Ok(contribution) = funding_template.splice_in_sync( + Amount::from_sat(10_000), + feerate, + FeeRate::MAX, + &wallet, + ) { + let _ = self.node.funding_contributed( + channel_id, + counterparty_node_id, + contribution, + None, + ); + } + }, + Err(e) => { + assert!( + matches!(e, APIError::APIMisuseError { ref err } if err.contains("splice")), + "{:?}", + e + ); + }, + } + } + + fn splice_out(&self, counterparty_node_id: &PublicKey, channel_id: &ChannelId) { + // We conditionally splice out `MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS` only when the node + // has double the balance required to send a payment upon a `0xff` byte. We do this to + // ensure there's always liquidity available for a payment to succeed then. + let outbound_capacity_msat = self + .node + .list_channels() + .iter() + .find(|chan| chan.channel_id == *channel_id) + .map(|chan| chan.outbound_capacity_msat) + .unwrap(); + if outbound_capacity_msat < 20_000_000 { + return; + } + match self.node.splice_channel(channel_id, counterparty_node_id) { + Ok(funding_template) => { + let feerate = + funding_template.min_rbf_feerate().unwrap_or(self.current_feerate_sat_per_kw()); + let outputs = vec![TxOut { + value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS), + script_pubkey: self.wallet.get_change_script().unwrap(), + }]; + if let Ok(contribution) = + funding_template.splice_out(outputs, feerate, FeeRate::MAX) + { + let _ = self.node.funding_contributed( + channel_id, + counterparty_node_id, + contribution, + None, + ); + } + }, + Err(e) => { + assert!( + matches!(e, APIError::APIMisuseError { ref err } if err.contains("splice")), + "{:?}", + e + ); + }, + } + } + fn reload( &mut self, use_old_mons: u8, out: &Out, router: &'a FuzzRouter, chan_type: ChanType, ) { @@ -1151,6 +1314,13 @@ impl<'a> HarnessNode<'a> { } } +#[derive(Copy, Clone)] +enum MonitorUpdateSelector { + First, + Second, + Last, +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1496,28 +1666,10 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); - let sync_with_chain_state = - |node: &mut HarnessNode<'_>, chain_state: &ChainState, num_blocks: Option| { - let target_height = if let Some(num_blocks) = num_blocks { - std::cmp::min(node.height + num_blocks, chain_state.tip_height()) - } else { - chain_state.tip_height() - }; - while node.height < target_height { - node.height += 1; - let (header, txn) = chain_state.block_at(node.height); - let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); - if !txdata.is_empty() { - node.transactions_confirmed(header, &txdata, node.height); - } - node.best_block_updated(header, node.height); - } - }; - // Sync all nodes to tip to lock the funding. - sync_with_chain_state(&mut nodes[0], &chain_state, None); - sync_with_chain_state(&mut nodes[1], &chain_state, None); - sync_with_chain_state(&mut nodes[2], &chain_state, None); + nodes[0].sync_with_chain_state(&chain_state, None); + nodes[1].sync_with_chain_state(&chain_state, None); + nodes[2].sync_with_chain_state(&chain_state, None); lock_fundings(&nodes); @@ -2094,121 +2246,6 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let complete_first = |v: &mut Vec<_>| if !v.is_empty() { Some(v.remove(0)) } else { None }; - let complete_second = |v: &mut Vec<_>| if v.len() > 1 { Some(v.remove(1)) } else { None }; - let complete_monitor_update = - |monitor: &Arc, - chan_funding, - compl_selector: &dyn Fn(&mut Vec<(u64, Vec)>) -> Option<(u64, Vec)>| { - if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_funding) { - assert!( - state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), - "updates should be sorted by id" - ); - if let Some((id, data)) = compl_selector(&mut state.pending_monitors) { - monitor.chain_monitor.channel_monitor_updated(*chan_funding, id).unwrap(); - if id > state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } - } - } - }; - let complete_all_monitor_updates = |monitor: &Arc, chan_id| { - if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { - assert!( - state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), - "updates should be sorted by id" - ); - for (id, data) in state.pending_monitors.drain(..) { - monitor.chain_monitor.channel_monitor_updated(*chan_id, id).unwrap(); - if id > state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } - } - } - }; - - let splice_channel = - |node: &HarnessNode<'_>, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId, - f: &dyn Fn( - FundingTemplate, - ) -> Result| { - match node.splice_channel(channel_id, counterparty_node_id) { - Ok(funding_template) => { - if let Ok(contribution) = f(funding_template) { - let _ = node.funding_contributed( - channel_id, - counterparty_node_id, - contribution, - None, - ); - } - }, - Err(e) => { - assert!( - matches!(e, APIError::APIMisuseError { ref err } if err.contains("splice")), - "{:?}", - e - ); - }, - } - }; - - let splice_in = |node: &HarnessNode<'_>, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId| { - let wallet = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); - let funding_feerate_sat_per_kw = node.fee_estimator.feerate_sat_per_kw(); - splice_channel( - node, - counterparty_node_id, - channel_id, - &move |funding_template: FundingTemplate| { - let feerate = - funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); - funding_template.splice_in_sync( - Amount::from_sat(10_000), - feerate, - FeeRate::MAX, - &wallet, - ) - }, - ); - }; - - let splice_out = |node: &HarnessNode<'_>, - counterparty_node_id: &PublicKey, - channel_id: &ChannelId| { - let outbound_capacity_msat = node - .list_channels() - .iter() - .find(|chan| chan.channel_id == *channel_id) - .map(|chan| chan.outbound_capacity_msat) - .unwrap(); - if outbound_capacity_msat < 20_000_000 { - return; - } - let funding_feerate_sat_per_kw = node.fee_estimator.feerate_sat_per_kw(); - splice_channel( - node, - counterparty_node_id, - channel_id, - &move |funding_template: FundingTemplate| { - let feerate = - funding_template.min_rbf_feerate().unwrap_or(funding_feerate_sat_per_kw); - let outputs = vec![TxOut { - value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS), - script_pubkey: node.wallet.get_change_script().unwrap(), - }]; - funding_template.splice_out(outputs, feerate, FeeRate::MAX) - }, - ); - }; - let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { let source = &nodes[source_idx]; @@ -2317,22 +2354,22 @@ pub fn do_test(data: &[u8], out: Out) { 0x08 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&nodes[0].monitor, id); + nodes[0].complete_all_monitor_updates(id); } }, 0x09 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&nodes[1].monitor, id); + nodes[1].complete_all_monitor_updates(id); } }, 0x0a => { for id in &chan_bc_ids { - complete_all_monitor_updates(&nodes[1].monitor, id); + nodes[1].complete_all_monitor_updates(id); } }, 0x0b => { for id in &chan_bc_ids { - complete_all_monitor_updates(&nodes[2].monitor, id); + nodes[2].complete_all_monitor_updates(id); } }, @@ -2488,82 +2525,40 @@ pub fn do_test(data: &[u8], out: Out) { send_mpp_direct(0, 1, &[chan_a_id, chan_a_id, chan_a_id], 1_000_000, &mut p_ctr) }, - 0x80 => { - let mut max_feerate = nodes[0].last_htlc_clear_fee; - if matches!(chan_type, ChanType::Legacy) { - max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; - } - if nodes[0].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 - > max_feerate - { - nodes[0].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); - } - nodes[0].timer_tick_occurred(); - }, - 0x81 => { - nodes[0].fee_estimator.ret_val.store(253, atomic::Ordering::Release); - nodes[0].timer_tick_occurred(); - }, - 0x84 => { - let mut max_feerate = nodes[1].last_htlc_clear_fee; - if matches!(chan_type, ChanType::Legacy) { - max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; - } - if nodes[1].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 - > max_feerate - { - nodes[1].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); - } - nodes[1].timer_tick_occurred(); - }, - 0x85 => { - nodes[1].fee_estimator.ret_val.store(253, atomic::Ordering::Release); - nodes[1].timer_tick_occurred(); - }, - 0x88 => { - let mut max_feerate = nodes[2].last_htlc_clear_fee; - if matches!(chan_type, ChanType::Legacy) { - max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32; - } - if nodes[2].fee_estimator.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 - > max_feerate - { - nodes[2].fee_estimator.ret_val.store(max_feerate, atomic::Ordering::Release); - } - nodes[2].timer_tick_occurred(); - }, - 0x89 => { - nodes[2].fee_estimator.ret_val.store(253, atomic::Ordering::Release); - nodes[2].timer_tick_occurred(); - }, + 0x80 => nodes[0].bump_fee_estimate(chan_type), + 0x81 => nodes[0].reset_fee_estimate(), + 0x84 => nodes[1].bump_fee_estimate(chan_type), + 0x85 => nodes[1].reset_fee_estimate(), + 0x88 => nodes[2].bump_fee_estimate(chan_type), + 0x89 => nodes[2].reset_fee_estimate(), 0xa0 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - splice_in(&nodes[0], &cp_node_id, &chan_a_id); + nodes[0].splice_in(&cp_node_id, &chan_a_id); }, 0xa1 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[0].get_our_node_id(); - splice_in(&nodes[1], &cp_node_id, &chan_a_id); + nodes[1].splice_in(&cp_node_id, &chan_a_id); }, 0xa2 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[2].get_our_node_id(); - splice_in(&nodes[1], &cp_node_id, &chan_b_id); + nodes[1].splice_in(&cp_node_id, &chan_b_id); }, 0xa3 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - splice_in(&nodes[2], &cp_node_id, &chan_b_id); + nodes[2].splice_in(&cp_node_id, &chan_b_id); }, 0xa4 => { @@ -2571,55 +2566,55 @@ pub fn do_test(data: &[u8], out: Out) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - splice_out(&nodes[0], &cp_node_id, &chan_a_id); + nodes[0].splice_out(&cp_node_id, &chan_a_id); }, 0xa5 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[0].get_our_node_id(); - splice_out(&nodes[1], &cp_node_id, &chan_a_id); + nodes[1].splice_out(&cp_node_id, &chan_a_id); }, 0xa6 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[2].get_our_node_id(); - splice_out(&nodes[1], &cp_node_id, &chan_b_id); + nodes[1].splice_out(&cp_node_id, &chan_b_id); }, 0xa7 => { if !cfg!(splicing) { test_return!(); } let cp_node_id = nodes[1].get_our_node_id(); - splice_out(&nodes[2], &cp_node_id, &chan_b_id); + nodes[2].splice_out(&cp_node_id, &chan_b_id); }, // Sync node by 1 block to cover confirmation of a transaction. 0xa8 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[0], &chain_state, Some(1)); + nodes[0].sync_with_chain_state(&chain_state, Some(1)); }, 0xa9 => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[1], &chain_state, Some(1)); + nodes[1].sync_with_chain_state(&chain_state, Some(1)); }, 0xaa => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[2], &chain_state, Some(1)); + nodes[2].sync_with_chain_state(&chain_state, Some(1)); }, // Sync node to chain tip to cover confirmation of a transaction post-reorg-risk. 0xab => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[0], &chain_state, None); + nodes[0].sync_with_chain_state(&chain_state, None); }, 0xac => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[1], &chain_state, None); + nodes[1].sync_with_chain_state(&chain_state, None); }, 0xad => { chain_state.confirm_pending_txs(); - sync_with_chain_state(&mut nodes[2], &chain_state, None); + nodes[2].sync_with_chain_state(&chain_state, None); }, 0xb0 | 0xb1 | 0xb2 => { @@ -2740,65 +2735,65 @@ pub fn do_test(data: &[u8], out: Out) { 0xf0 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, &complete_first); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf1 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, &complete_second); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xf2 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, &Vec::pop); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xf4 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_first); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf5 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_second); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xf6 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xf8 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_first); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf9 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_second); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xfa => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xfc => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &complete_first); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xfd => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &complete_second); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xfe => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &Vec::pop); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, @@ -2856,12 +2851,12 @@ pub fn do_test(data: &[u8], out: Out) { } // Next, make sure no monitor updates are pending for id in &chan_ab_ids { - complete_all_monitor_updates(&nodes[0].monitor, id); - complete_all_monitor_updates(&nodes[1].monitor, id); + nodes[0].complete_all_monitor_updates(id); + nodes[1].complete_all_monitor_updates(id); } for id in &chan_bc_ids { - complete_all_monitor_updates(&nodes[1].monitor, id); - complete_all_monitor_updates(&nodes[2].monitor, id); + nodes[1].complete_all_monitor_updates(id); + nodes[2].complete_all_monitor_updates(id); } // Then, make sure any current forwards make their way to their destination if process_msg_events!(0, false, ProcessMessages::AllMessages) { @@ -2952,12 +2947,9 @@ pub fn do_test(data: &[u8], out: Out) { ); } - nodes[0].last_htlc_clear_fee = - nodes[0].fee_estimator.ret_val.load(atomic::Ordering::Acquire); - nodes[1].last_htlc_clear_fee = - nodes[1].fee_estimator.ret_val.load(atomic::Ordering::Acquire); - nodes[2].last_htlc_clear_fee = - nodes[2].fee_estimator.ret_val.load(atomic::Ordering::Acquire); + nodes[0].record_last_htlc_clear_fee(); + nodes[1].record_last_htlc_clear_fee(); + nodes[2].record_last_htlc_clear_fee(); }, _ => test_return!(), } From 1da1cccc1c7164769f8cbc0ed83a4233a3b17f1e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 28 Apr 2026 11:40:02 +0200 Subject: [PATCH 07/11] Route chanmon messages through EventQueues Replace the four directional message vectors with one queue owner. Move per-node queue draining, middle-node routing, and disconnect cleanup into EventQueues so routing behavior lives with the queue state. --- fuzz/src/chanmon_consistency.rs | 365 ++++++++++++++++---------------- 1 file changed, 182 insertions(+), 183 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index fb911eb644a..24627702aac 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1321,6 +1321,158 @@ enum MonitorUpdateSelector { Last, } +struct EventQueues { + ab: Vec, + ba: Vec, + bc: Vec, + cb: Vec, +} + +impl EventQueues { + fn new() -> Self { + Self { ab: Vec::new(), ba: Vec::new(), bc: Vec::new(), cb: Vec::new() } + } + + fn take_for_node(&mut self, node_idx: usize) -> Vec { + match node_idx { + 0 => { + let mut events = Vec::new(); + mem::swap(&mut events, &mut self.ab); + events + }, + 1 => { + let mut events = Vec::new(); + mem::swap(&mut events, &mut self.ba); + events.extend_from_slice(&self.bc[..]); + self.bc.clear(); + events + }, + 2 => { + let mut events = Vec::new(); + mem::swap(&mut events, &mut self.cb); + events + }, + _ => panic!("invalid node index"), + } + } + + fn push_for_node(&mut self, node_idx: usize, event: MessageSendEvent) { + match node_idx { + 0 => self.ab.push(event), + 2 => self.cb.push(event), + _ => panic!("cannot directly queue messages for node {}", node_idx), + } + } + + fn extend_for_node>( + &mut self, node_idx: usize, events: I, + ) { + match node_idx { + 0 => self.ab.extend(events), + 2 => self.cb.extend(events), + _ => panic!("cannot directly queue messages for node {}", node_idx), + } + } + + fn route_from_middle<'a, I: IntoIterator>( + &mut self, excess_events: I, expect_drop_node: Option, nodes: &[HarnessNode<'a>; 3], + ) { + // Push any events from Node B onto queues.ba and queues.bc. + let a_id = nodes[0].get_our_node_id(); + let expect_drop_id = expect_drop_node.map(|id| nodes[id].get_our_node_id()); + for event in excess_events { + let push_a = match event { + MessageSendEvent::UpdateHTLCs { ref node_id, .. } + | MessageSendEvent::SendRevokeAndACK { ref node_id, .. } + | MessageSendEvent::SendChannelReestablish { ref node_id, .. } + | MessageSendEvent::SendStfu { ref node_id, .. } + | MessageSendEvent::SendSpliceInit { ref node_id, .. } + | MessageSendEvent::SendSpliceAck { ref node_id, .. } + | MessageSendEvent::SendSpliceLocked { ref node_id, .. } + | MessageSendEvent::SendTxAddInput { ref node_id, .. } + | MessageSendEvent::SendTxAddOutput { ref node_id, .. } + | MessageSendEvent::SendTxRemoveInput { ref node_id, .. } + | MessageSendEvent::SendTxRemoveOutput { ref node_id, .. } + | MessageSendEvent::SendTxComplete { ref node_id, .. } + | MessageSendEvent::SendTxAbort { ref node_id, .. } + | MessageSendEvent::SendTxInitRbf { ref node_id, .. } + | MessageSendEvent::SendTxAckRbf { ref node_id, .. } + | MessageSendEvent::SendTxSignatures { ref node_id, .. } + | MessageSendEvent::SendChannelUpdate { ref node_id, .. } => { + if Some(*node_id) == expect_drop_id { + panic!( + "peer_disconnected should drop msgs bound for the disconnected peer" + ); + } + *node_id == a_id + }, + MessageSendEvent::HandleError { ref action, ref node_id } => { + assert_action_timeout_awaiting_response(action); + if Some(*node_id) == expect_drop_id { + panic!( + "peer_disconnected should drop msgs bound for the disconnected peer" + ); + } + *node_id == a_id + }, + MessageSendEvent::SendChannelReady { .. } + | MessageSendEvent::SendAnnouncementSignatures { .. } + | MessageSendEvent::BroadcastChannelUpdate { .. } => continue, + _ => panic!("Unhandled message event {:?}", event), + }; + if push_a { + self.ba.push(event); + } else { + self.bc.push(event); + } + } + } + + fn drain_on_disconnect(&mut self, edge_node: usize, nodes: &[HarnessNode<'_>; 3]) { + match edge_node { + 0 => { + for event in nodes[0].get_and_clear_pending_msg_events() { + match event { + MessageSendEvent::UpdateHTLCs { .. } => {}, + MessageSendEvent::SendRevokeAndACK { .. } => {}, + MessageSendEvent::SendChannelReestablish { .. } => {}, + MessageSendEvent::SendStfu { .. } => {}, + MessageSendEvent::SendChannelReady { .. } => {}, + MessageSendEvent::SendAnnouncementSignatures { .. } => {}, + MessageSendEvent::BroadcastChannelUpdate { .. } => {}, + MessageSendEvent::SendChannelUpdate { .. } => {}, + MessageSendEvent::HandleError { ref action, .. } => { + assert_action_timeout_awaiting_response(action); + }, + _ => panic!("Unhandled message event"), + } + } + self.route_from_middle(nodes[1].get_and_clear_pending_msg_events(), Some(0), nodes); + }, + 2 => { + for event in nodes[2].get_and_clear_pending_msg_events() { + match event { + MessageSendEvent::UpdateHTLCs { .. } => {}, + MessageSendEvent::SendRevokeAndACK { .. } => {}, + MessageSendEvent::SendChannelReestablish { .. } => {}, + MessageSendEvent::SendStfu { .. } => {}, + MessageSendEvent::SendChannelReady { .. } => {}, + MessageSendEvent::SendAnnouncementSignatures { .. } => {}, + MessageSendEvent::BroadcastChannelUpdate { .. } => {}, + MessageSendEvent::SendChannelUpdate { .. } => {}, + MessageSendEvent::HandleError { ref action, .. } => { + assert_action_timeout_awaiting_response(action); + }, + _ => panic!("Unhandled message event"), + } + } + self.route_from_middle(nodes[1].get_and_clear_pending_msg_events(), Some(2), nodes); + }, + _ => panic!("unsupported disconnected edge"), + } + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1691,10 +1843,7 @@ pub fn do_test(data: &[u8], out: Out) { 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(); - let mut cb_events = Vec::new(); + let mut queues = EventQueues::new(); for node in &mut nodes { node.serialized_manager = node.encode(); @@ -1727,97 +1876,6 @@ pub fn do_test(data: &[u8], out: Out) { } loop { - // Push any events from Node B onto ba_events and bc_events - macro_rules! push_excess_b_events { - ($excess_events: expr, $expect_drop_node: expr) => { { - let a_id = nodes[0].get_our_node_id(); - let expect_drop_node: Option = $expect_drop_node; - let expect_drop_id = if let Some(id) = expect_drop_node { Some(nodes[id].get_our_node_id()) } else { None }; - for event in $excess_events { - let push_a = match event { - MessageSendEvent::UpdateHTLCs { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendRevokeAndACK { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendChannelReestablish { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendStfu { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendSpliceInit { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendSpliceAck { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendSpliceLocked { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxAddInput { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxAddOutput { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxRemoveInput { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxRemoveOutput { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxComplete { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxAbort { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxInitRbf { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxAckRbf { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendTxSignatures { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::SendChannelReady { .. } => continue, - MessageSendEvent::SendAnnouncementSignatures { .. } => continue, - MessageSendEvent::BroadcastChannelUpdate { .. } => continue, - MessageSendEvent::SendChannelUpdate { ref node_id, .. } => { - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - MessageSendEvent::HandleError { ref action, ref node_id } => { - assert_action_timeout_awaiting_response(action); - if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } - *node_id == a_id - }, - _ => panic!("Unhandled message event {:?}", event), - }; - if push_a { ba_events.push(event); } else { bc_events.push(event); } - } - } } - } - // While delivering messages, we select across three possible message selection processes // to ensure we get as much coverage as possible. See the individual enum variants for more // details. @@ -1837,21 +1895,7 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! process_msg_events { ($node: expr, $corrupt_forward: expr, $limit_events: expr) => { { - let mut events = if $node == 1 { - let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut ba_events); - new_events.extend_from_slice(&bc_events[..]); - bc_events.clear(); - new_events - } else if $node == 0 { - let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut ab_events); - new_events - } else { - let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut cb_events); - new_events - }; + let mut events = queues.take_for_node($node); let mut new_events = Vec::new(); if $limit_events != ProcessMessages::OnePendingMessage { new_events = nodes[$node].get_and_clear_pending_msg_events(); @@ -2059,13 +2103,14 @@ pub fn do_test(data: &[u8], out: Out) { } } if $node == 1 { - push_excess_b_events!(extra_ev.into_iter().chain(events_iter), None); + let remaining = extra_ev.into_iter().chain(events_iter).collect::>(); + queues.route_from_middle(remaining, None, &nodes); } else if $node == 0 { - if let Some(ev) = extra_ev { ab_events.push(ev); } - for event in events_iter { ab_events.push(event); } + if let Some(ev) = extra_ev { queues.push_for_node(0, ev); } + queues.extend_for_node(0, events_iter); } else { - if let Some(ev) = extra_ev { cb_events.push(ev); } - for event in events_iter { cb_events.push(event); } + if let Some(ev) = extra_ev { queues.push_for_node(2, ev); } + queues.extend_for_node(2, events_iter); } had_events } } @@ -2077,58 +2122,6 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - macro_rules! drain_msg_events_on_disconnect { - ($counterparty_id: expr) => {{ - if $counterparty_id == 0 { - for event in nodes[0].get_and_clear_pending_msg_events() { - match event { - MessageSendEvent::UpdateHTLCs { .. } => {}, - MessageSendEvent::SendRevokeAndACK { .. } => {}, - MessageSendEvent::SendChannelReestablish { .. } => {}, - MessageSendEvent::SendStfu { .. } => {}, - MessageSendEvent::SendChannelReady { .. } => {}, - MessageSendEvent::SendAnnouncementSignatures { .. } => {}, - MessageSendEvent::BroadcastChannelUpdate { .. } => {}, - MessageSendEvent::SendChannelUpdate { .. } => {}, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); - }, - _ => panic!("Unhandled message event"), - } - } - push_excess_b_events!( - nodes[1].get_and_clear_pending_msg_events().drain(..), - Some(0) - ); - ab_events.clear(); - ba_events.clear(); - } else { - for event in nodes[2].get_and_clear_pending_msg_events() { - match event { - MessageSendEvent::UpdateHTLCs { .. } => {}, - MessageSendEvent::SendRevokeAndACK { .. } => {}, - MessageSendEvent::SendChannelReestablish { .. } => {}, - MessageSendEvent::SendStfu { .. } => {}, - MessageSendEvent::SendChannelReady { .. } => {}, - MessageSendEvent::SendAnnouncementSignatures { .. } => {}, - MessageSendEvent::BroadcastChannelUpdate { .. } => {}, - MessageSendEvent::SendChannelUpdate { .. } => {}, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); - }, - _ => panic!("Unhandled message event"), - } - } - push_excess_b_events!( - nodes[1].get_and_clear_pending_msg_events().drain(..), - Some(2) - ); - bc_events.clear(); - cb_events.clear(); - } - }}; - } - macro_rules! process_events { ($node: expr, $fail: expr) => {{ // Multiple HTLCs can resolve for the same payment hash, so deduplicate @@ -2378,7 +2371,9 @@ pub fn do_test(data: &[u8], out: Out) { nodes[0].peer_disconnected(nodes[1].get_our_node_id()); nodes[1].peer_disconnected(nodes[0].get_our_node_id()); peers_ab_disconnected = true; - drain_msg_events_on_disconnect!(0); + queues.drain_on_disconnect(0, &nodes); + queues.ab.clear(); + queues.ba.clear(); } }, 0x0d => { @@ -2386,7 +2381,9 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].peer_disconnected(nodes[2].get_our_node_id()); nodes[2].peer_disconnected(nodes[1].get_our_node_id()); peers_bc_disconnected = true; - drain_msg_events_on_disconnect!(2); + queues.drain_on_disconnect(2, &nodes); + queues.bc.clear(); + queues.cb.clear(); } }, 0x0e => { @@ -2623,12 +2620,13 @@ pub fn do_test(data: &[u8], out: Out) { if !peers_ab_disconnected { nodes[1].peer_disconnected(nodes[0].get_our_node_id()); peers_ab_disconnected = true; - push_excess_b_events!( - nodes[1].get_and_clear_pending_msg_events().drain(..), - Some(0) + queues.route_from_middle( + nodes[1].get_and_clear_pending_msg_events(), + Some(0), + &nodes, ); - ab_events.clear(); - ba_events.clear(); + queues.ab.clear(); + queues.ba.clear(); } nodes[0].reload(v, &out, &router, chan_type); }, @@ -2639,15 +2637,15 @@ pub fn do_test(data: &[u8], out: Out) { nodes[0].peer_disconnected(nodes[1].get_our_node_id()); peers_ab_disconnected = true; nodes[0].get_and_clear_pending_msg_events(); - ab_events.clear(); - ba_events.clear(); + queues.ab.clear(); + queues.ba.clear(); } if !peers_bc_disconnected { nodes[2].peer_disconnected(nodes[1].get_our_node_id()); peers_bc_disconnected = true; nodes[2].get_and_clear_pending_msg_events(); - bc_events.clear(); - cb_events.clear(); + queues.bc.clear(); + queues.cb.clear(); } nodes[1].reload(v, &out, &router, chan_type); }, @@ -2657,12 +2655,13 @@ pub fn do_test(data: &[u8], out: Out) { if !peers_bc_disconnected { nodes[1].peer_disconnected(nodes[2].get_our_node_id()); peers_bc_disconnected = true; - push_excess_b_events!( - nodes[1].get_and_clear_pending_msg_events().drain(..), - Some(2) + queues.route_from_middle( + nodes[1].get_and_clear_pending_msg_events(), + Some(2), + &nodes, ); - bc_events.clear(); - cb_events.clear(); + queues.bc.clear(); + queues.cb.clear(); } nodes[2].reload(v, &out, &router, chan_type); }, From 30eebef9303ab5bf9d076941b9a00c845d1fd014 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:07:32 +0200 Subject: [PATCH 08/11] Extract chanmon harness peer links Represent each channel pair as a peer link with its channel ids and disconnect state. Link methods now own peer reconnect, disconnect, and monitor-update operations for that channel group. --- fuzz/src/chanmon_consistency.rs | 325 +++++++++++++++----------------- 1 file changed, 151 insertions(+), 174 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 24627702aac..3ece47fc7e0 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1428,6 +1428,20 @@ impl EventQueues { } } + fn clear_link(&mut self, link: &PeerLink) { + match (link.node_a, link.node_b) { + (0, 1) | (1, 0) => { + self.ab.clear(); + self.ba.clear(); + }, + (1, 2) | (2, 1) => { + self.bc.clear(); + self.cb.clear(); + }, + _ => panic!("unsupported link"), + } + } + fn drain_on_disconnect(&mut self, edge_node: usize, nodes: &[HarnessNode<'_>; 3]) { match edge_node { 0 => { @@ -1473,6 +1487,109 @@ impl EventQueues { } } +struct PeerLink { + node_a: usize, + node_b: usize, + channel_ids: [ChannelId; 3], + disconnected: bool, +} + +impl PeerLink { + fn new(node_a: usize, node_b: usize, channel_ids: [ChannelId; 3]) -> Self { + Self { node_a, node_b, channel_ids, disconnected: false } + } + + fn first_channel_id(&self) -> ChannelId { + self.channel_ids[0] + } + + fn channel_ids(&self) -> &[ChannelId; 3] { + &self.channel_ids + } + + fn complete_all_monitor_updates(&self, nodes: &[HarnessNode<'_>; 3]) { + for id in &self.channel_ids { + nodes[self.node_a].complete_all_monitor_updates(id); + nodes[self.node_b].complete_all_monitor_updates(id); + } + } + + fn complete_monitor_updates_for_node( + &self, node_idx: usize, nodes: &[HarnessNode<'_>; 3], selector: MonitorUpdateSelector, + ) { + assert!(node_idx == self.node_a || node_idx == self.node_b); + for id in &self.channel_ids { + nodes[node_idx].complete_monitor_update(id, selector); + } + } + + fn disconnect(&mut self, nodes: &[HarnessNode<'_>; 3], queues: &mut EventQueues) { + if self.disconnected { + return; + } + let node_a_id = nodes[self.node_a].get_our_node_id(); + let node_b_id = nodes[self.node_b].get_our_node_id(); + nodes[self.node_a].peer_disconnected(node_b_id); + nodes[self.node_b].peer_disconnected(node_a_id); + self.disconnected = true; + let edge_node = if self.node_a == 1 { + self.node_b + } else if self.node_b == 1 { + self.node_a + } else { + panic!("unsupported link topology") + }; + queues.drain_on_disconnect(edge_node, nodes); + queues.clear_link(self); + } + + fn reconnect(&mut self, nodes: &[HarnessNode<'_>; 3]) { + if !self.disconnected { + return; + } + let node_a_id = nodes[self.node_a].get_our_node_id(); + let node_b_id = nodes[self.node_b].get_our_node_id(); + let init_b = Init { + features: nodes[self.node_b].init_features(), + networks: None, + remote_network_address: None, + }; + nodes[self.node_a].peer_connected(node_b_id, &init_b, true).unwrap(); + let init_a = Init { + features: nodes[self.node_a].init_features(), + networks: None, + remote_network_address: None, + }; + nodes[self.node_b].peer_connected(node_a_id, &init_a, false).unwrap(); + self.disconnected = false; + } + + fn disconnect_for_reload( + &mut self, restarted_node: usize, nodes: &[HarnessNode<'_>; 3], queues: &mut EventQueues, + ) { + if self.disconnected { + return; + } + assert!(restarted_node == self.node_a || restarted_node == self.node_b); + + let remaining_node = if restarted_node == self.node_a { self.node_b } else { self.node_a }; + let restarted_node_id = nodes[restarted_node].get_our_node_id(); + nodes[remaining_node].peer_disconnected(restarted_node_id); + self.disconnected = true; + + if remaining_node == 1 { + queues.route_from_middle( + nodes[1].get_and_clear_pending_msg_events(), + Some(restarted_node), + nodes, + ); + } else { + nodes[remaining_node].get_and_clear_pending_msg_events(); + } + queues.clear_link(self); + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1835,14 +1952,14 @@ pub fn do_test(data: &[u8], out: Out) { let node_c_chans = nodes[2].list_usable_channels(); [node_c_chans[0].channel_id, node_c_chans[1].channel_id, node_c_chans[2].channel_id] }; + let mut ab_link = PeerLink::new(0, 1, chan_ab_ids); + let mut bc_link = PeerLink::new(1, 2, chan_bc_ids); // Keep old names for backward compatibility in existing code - let chan_a_id = chan_ab_ids[0]; - let chan_b_id = chan_bc_ids[0]; + let chan_a_id = ab_link.first_channel_id(); + let chan_b_id = bc_link.first_channel_id(); let mut p_ctr: u64 = 0; - let mut peers_ab_disconnected = false; - let mut peers_bc_disconnected = false; let mut queues = EventQueues::new(); for node in &mut nodes { @@ -2346,80 +2463,30 @@ pub fn do_test(data: &[u8], out: Out) { 0x06 => nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::Completed), 0x08 => { - for id in &chan_ab_ids { + for id in ab_link.channel_ids() { nodes[0].complete_all_monitor_updates(id); } }, 0x09 => { - for id in &chan_ab_ids { + for id in ab_link.channel_ids() { nodes[1].complete_all_monitor_updates(id); } }, 0x0a => { - for id in &chan_bc_ids { + for id in bc_link.channel_ids() { nodes[1].complete_all_monitor_updates(id); } }, 0x0b => { - for id in &chan_bc_ids { + for id in bc_link.channel_ids() { nodes[2].complete_all_monitor_updates(id); } }, - 0x0c => { - if !peers_ab_disconnected { - nodes[0].peer_disconnected(nodes[1].get_our_node_id()); - nodes[1].peer_disconnected(nodes[0].get_our_node_id()); - peers_ab_disconnected = true; - queues.drain_on_disconnect(0, &nodes); - queues.ab.clear(); - queues.ba.clear(); - } - }, - 0x0d => { - if !peers_bc_disconnected { - nodes[1].peer_disconnected(nodes[2].get_our_node_id()); - nodes[2].peer_disconnected(nodes[1].get_our_node_id()); - peers_bc_disconnected = true; - queues.drain_on_disconnect(2, &nodes); - queues.bc.clear(); - queues.cb.clear(); - } - }, - 0x0e => { - if peers_ab_disconnected { - let init_1 = Init { - features: nodes[1].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[0].peer_connected(nodes[1].get_our_node_id(), &init_1, true).unwrap(); - let init_0 = Init { - features: nodes[0].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[1].peer_connected(nodes[0].get_our_node_id(), &init_0, false).unwrap(); - peers_ab_disconnected = false; - } - }, - 0x0f => { - if peers_bc_disconnected { - let init_2 = Init { - features: nodes[2].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[1].peer_connected(nodes[2].get_our_node_id(), &init_2, true).unwrap(); - let init_1 = Init { - features: nodes[1].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[2].peer_connected(nodes[1].get_our_node_id(), &init_1, false).unwrap(); - peers_bc_disconnected = false; - } - }, + 0x0c => ab_link.disconnect(&nodes, &mut queues), + 0x0d => bc_link.disconnect(&nodes, &mut queues), + 0x0e => ab_link.reconnect(&nodes), + 0x0f => bc_link.reconnect(&nodes), 0x10 => process_msg_noret!(0, true, ProcessMessages::AllMessages), 0x11 => process_msg_noret!(0, false, ProcessMessages::AllMessages), @@ -2617,52 +2684,20 @@ pub fn do_test(data: &[u8], out: Out) { 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 !peers_ab_disconnected { - nodes[1].peer_disconnected(nodes[0].get_our_node_id()); - peers_ab_disconnected = true; - queues.route_from_middle( - nodes[1].get_and_clear_pending_msg_events(), - Some(0), - &nodes, - ); - queues.ab.clear(); - queues.ba.clear(); - } + ab_link.disconnect_for_reload(0, &nodes, &mut queues); nodes[0].reload(v, &out, &router, chan_type); }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on // the value of `v` we're matching. - if !peers_ab_disconnected { - nodes[0].peer_disconnected(nodes[1].get_our_node_id()); - peers_ab_disconnected = true; - nodes[0].get_and_clear_pending_msg_events(); - queues.ab.clear(); - queues.ba.clear(); - } - if !peers_bc_disconnected { - nodes[2].peer_disconnected(nodes[1].get_our_node_id()); - peers_bc_disconnected = true; - nodes[2].get_and_clear_pending_msg_events(); - queues.bc.clear(); - queues.cb.clear(); - } + ab_link.disconnect_for_reload(1, &nodes, &mut queues); + bc_link.disconnect_for_reload(1, &nodes, &mut queues); nodes[1].reload(v, &out, &router, chan_type); }, 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 !peers_bc_disconnected { - nodes[1].peer_disconnected(nodes[2].get_our_node_id()); - peers_bc_disconnected = true; - queues.route_from_middle( - nodes[1].get_and_clear_pending_msg_events(), - Some(2), - &nodes, - ); - queues.bc.clear(); - queues.cb.clear(); - } + bc_link.disconnect_for_reload(2, &nodes, &mut queues); nodes[2].reload(v, &out, &router, chan_type); }, @@ -2733,67 +2768,43 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xf0 => { - for id in &chan_ab_ids { - nodes[0].complete_monitor_update(id, MonitorUpdateSelector::First); - } + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::First) }, 0xf1 => { - for id in &chan_ab_ids { - nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Second); - } + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Second) }, 0xf2 => { - for id in &chan_ab_ids { - nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Last); - } + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Last) }, 0xf4 => { - for id in &chan_ab_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); - } + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) }, 0xf5 => { - for id in &chan_ab_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); - } + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) }, 0xf6 => { - for id in &chan_ab_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); - } + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) }, 0xf8 => { - for id in &chan_bc_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); - } + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) }, 0xf9 => { - for id in &chan_bc_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); - } + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) }, 0xfa => { - for id in &chan_bc_ids { - nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); - } + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) }, 0xfc => { - for id in &chan_bc_ids { - nodes[2].complete_monitor_update(id, MonitorUpdateSelector::First); - } + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::First) }, 0xfd => { - for id in &chan_bc_ids { - nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Second); - } + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Second) }, 0xfe => { - for id in &chan_bc_ids { - nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Last); - } + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Last) }, 0xff => { @@ -2801,36 +2812,8 @@ pub fn do_test(data: &[u8], out: Out) { // after we resolve all pending events. // First, make sure peers are all connected to each other - if peers_ab_disconnected { - let init_1 = Init { - features: nodes[1].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[0].peer_connected(nodes[1].get_our_node_id(), &init_1, true).unwrap(); - let init_0 = Init { - features: nodes[0].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[1].peer_connected(nodes[0].get_our_node_id(), &init_0, false).unwrap(); - peers_ab_disconnected = false; - } - if peers_bc_disconnected { - let init_2 = Init { - features: nodes[2].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[1].peer_connected(nodes[2].get_our_node_id(), &init_2, true).unwrap(); - let init_1 = Init { - features: nodes[1].init_features(), - networks: None, - remote_network_address: None, - }; - nodes[2].peer_connected(nodes[1].get_our_node_id(), &init_1, false).unwrap(); - peers_bc_disconnected = false; - } + ab_link.reconnect(&nodes); + bc_link.reconnect(&nodes); for op in SUPPORTED_SIGNER_OPS { nodes[0].keys_manager.enable_op_for_all_signers(op); @@ -2849,14 +2832,8 @@ pub fn do_test(data: &[u8], out: Out) { panic!("It may take may iterations to settle the state, but it should not take forever"); } // Next, make sure no monitor updates are pending - for id in &chan_ab_ids { - nodes[0].complete_all_monitor_updates(id); - nodes[1].complete_all_monitor_updates(id); - } - for id in &chan_bc_ids { - nodes[1].complete_all_monitor_updates(id); - nodes[2].complete_all_monitor_updates(id); - } + ab_link.complete_all_monitor_updates(&nodes); + bc_link.complete_all_monitor_updates(&nodes); // 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; @@ -2933,13 +2910,13 @@ pub fn do_test(data: &[u8], out: Out) { } // Finally, make sure that at least one end of each channel can make a substantial payment - for &chan_id in &chan_ab_ids { + for &chan_id in ab_link.channel_ids() { assert!( send(0, 1, chan_id, 10_000_000, &mut p_ctr) || send(1, 0, chan_id, 10_000_000, &mut p_ctr) ); } - for &chan_id in &chan_bc_ids { + for &chan_id in bc_link.channel_ids() { assert!( send(1, 2, chan_id, 10_000_000, &mut p_ctr) || send(2, 1, chan_id, 10_000_000, &mut p_ctr) From 3657a5616a986f1a76807b25aef53d7d744cc577 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 15:51:28 +0200 Subject: [PATCH 09/11] Extract chanmon harness payment helpers Move payment bookkeeping into a payment tracker. Sending, resolving, claiming, and stuck-payment assertions now share one state owner instead of borrowing several local maps. --- fuzz/src/chanmon_consistency.rs | 1200 ++++++++++++++++--------------- 1 file changed, 614 insertions(+), 586 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 3ece47fc7e0..b88b36fac46 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -600,23 +600,6 @@ impl KeyProvider { } } -// Returns a bool indicating whether the payment failed. -#[inline] -fn check_payment_send_events(source: &ChanMan, sent_payment_id: PaymentId) -> bool { - for payment in source.list_recent_payments() { - match payment { - RecentPaymentDetails::Pending { payment_id, .. } if payment_id == sent_payment_id => { - return true; - }, - RecentPaymentDetails::Abandoned { payment_id, .. } if payment_id == sent_payment_id => { - return false; - }, - _ => {}, - } - } - return false; -} - type ChanMan<'a> = ChannelManager< Arc, Arc, @@ -629,297 +612,6 @@ type ChanMan<'a> = ChannelManager< Arc, >; -#[inline] -fn get_payment_secret_hash( - dest: &ChanMan, payment_ctr: &mut u64, - payment_preimages: &RefCell>, -) -> (PaymentSecret, PaymentHash) { - *payment_ctr += 1; - let mut payment_preimage = PaymentPreimage([0; 32]); - payment_preimage.0[0..8].copy_from_slice(&payment_ctr.to_be_bytes()); - let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); - let payment_secret = dest - .create_inbound_payment_for_hash(payment_hash, None, 3600, None) - .expect("create_inbound_payment_for_hash failed"); - assert!(payment_preimages.borrow_mut().insert(payment_hash, payment_preimage).is_none()); - (payment_secret, payment_hash) -} - -#[inline] -fn send_payment( - source: &ChanMan, dest: &ChanMan, dest_chan_id: ChannelId, amt: u64, - payment_secret: PaymentSecret, payment_hash: PaymentHash, payment_id: PaymentId, -) -> bool { - let (min_value_sendable, max_value_sendable, dest_scid) = source - .list_usable_channels() - .iter() - .find(|chan| chan.channel_id == dest_chan_id) - .map(|chan| { - ( - chan.next_outbound_htlc_minimum_msat, - chan.next_outbound_htlc_limit_msat, - chan.short_channel_id.unwrap_or(0), - ) - }) - .unwrap_or((0, 0, 0)); - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::from_node_id(source.get_our_node_id(), TEST_FINAL_CLTV), - amt, - ); - let route = Route { - paths: vec![Path { - hops: vec![RouteHop { - pubkey: dest.get_our_node_id(), - node_features: dest.node_features(), - short_channel_id: dest_scid, - channel_features: dest.channel_features(), - fee_msat: amt, - cltv_expiry_delta: 200, - maybe_announced_channel: true, - }], - blinded_tail: None, - }], - route_params: Some(route_params.clone()), - }; - let onion = RecipientOnionFields::secret_only(payment_secret, amt); - let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); - match res { - Err(err) => { - panic!("Errored with {:?} on initial payment send", err); - }, - Ok(()) => { - let expect_failure = amt < min_value_sendable || amt > max_value_sendable; - let succeeded = check_payment_send_events(source, payment_id); - assert_eq!(succeeded, !expect_failure); - succeeded - }, - } -} - -#[inline] -fn send_hop_payment( - source: &ChanMan, middle: &ChanMan, middle_chan_id: ChannelId, dest: &ChanMan, - dest_chan_id: ChannelId, amt: u64, payment_secret: PaymentSecret, payment_hash: PaymentHash, - payment_id: PaymentId, -) -> bool { - let (min_value_sendable, max_value_sendable, middle_scid) = source - .list_usable_channels() - .iter() - .find(|chan| chan.channel_id == middle_chan_id) - .map(|chan| { - ( - chan.next_outbound_htlc_minimum_msat, - chan.next_outbound_htlc_limit_msat, - chan.short_channel_id.unwrap_or(0), - ) - }) - .unwrap_or((0, 0, 0)); - let dest_scid = dest - .list_channels() - .iter() - .find(|chan| chan.channel_id == dest_chan_id) - .and_then(|chan| chan.short_channel_id) - .unwrap_or(0); - let first_hop_fee = 50_000; - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::from_node_id(source.get_our_node_id(), TEST_FINAL_CLTV), - amt, - ); - let route = Route { - paths: vec![Path { - hops: vec![ - RouteHop { - pubkey: middle.get_our_node_id(), - node_features: middle.node_features(), - short_channel_id: middle_scid, - channel_features: middle.channel_features(), - fee_msat: first_hop_fee, - cltv_expiry_delta: 100, - maybe_announced_channel: true, - }, - RouteHop { - pubkey: dest.get_our_node_id(), - node_features: dest.node_features(), - short_channel_id: dest_scid, - channel_features: dest.channel_features(), - fee_msat: amt, - cltv_expiry_delta: 200, - maybe_announced_channel: true, - }, - ], - blinded_tail: None, - }], - route_params: Some(route_params.clone()), - }; - let onion = RecipientOnionFields::secret_only(payment_secret, amt); - let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); - match res { - Err(err) => { - panic!("Errored with {:?} on initial payment send", err); - }, - Ok(()) => { - let sent_amt = amt + first_hop_fee; - let expect_failure = sent_amt < min_value_sendable || sent_amt > max_value_sendable; - let succeeded = check_payment_send_events(source, payment_id); - assert_eq!(succeeded, !expect_failure); - succeeded - }, - } -} - -/// Send an MPP payment directly from source to dest using multiple channels. -#[inline] -fn send_mpp_payment( - source: &ChanMan, dest: &ChanMan, dest_chan_ids: &[ChannelId], amt: u64, - payment_secret: PaymentSecret, payment_hash: PaymentHash, payment_id: PaymentId, -) -> bool { - let num_paths = dest_chan_ids.len(); - if num_paths == 0 { - return false; - } - - let amt_per_path = amt / num_paths as u64; - let mut paths = Vec::with_capacity(num_paths); - - let dest_chans = dest.list_channels(); - let dest_scids = dest_chan_ids.iter().map(|chan_id| { - dest_chans - .iter() - .find(|chan| chan.channel_id == *chan_id) - .and_then(|chan| chan.short_channel_id) - .unwrap() - }); - - for (i, dest_scid) in dest_scids.enumerate() { - let path_amt = if i == num_paths - 1 { - amt - amt_per_path * (num_paths as u64 - 1) - } else { - amt_per_path - }; - - paths.push(Path { - hops: vec![RouteHop { - pubkey: dest.get_our_node_id(), - node_features: dest.node_features(), - short_channel_id: dest_scid, - channel_features: dest.channel_features(), - fee_msat: path_amt, - cltv_expiry_delta: 200, - maybe_announced_channel: true, - }], - blinded_tail: None, - }); - } - - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::from_node_id(dest.get_our_node_id(), TEST_FINAL_CLTV), - amt, - ); - let route = Route { paths, route_params: Some(route_params) }; - let onion = RecipientOnionFields::secret_only(payment_secret, amt); - let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); - match res { - Err(_) => false, - Ok(()) => check_payment_send_events(source, payment_id), - } -} - -/// Send an MPP payment from source to dest via middle node. -/// Supports multiple channels on either or both hops. -#[inline] -fn send_mpp_hop_payment( - source: &ChanMan, middle: &ChanMan, middle_chan_ids: &[ChannelId], dest: &ChanMan, - dest_chan_ids: &[ChannelId], amt: u64, payment_secret: PaymentSecret, - payment_hash: PaymentHash, payment_id: PaymentId, -) -> bool { - // Create paths by pairing middle_scids with dest_scids - let num_paths = middle_chan_ids.len().max(dest_chan_ids.len()); - if num_paths == 0 { - return false; - } - - let first_hop_fee = 50_000; - let amt_per_path = amt / num_paths as u64; - let fee_per_path = first_hop_fee / num_paths as u64; - let mut paths = Vec::with_capacity(num_paths); - - let middle_chans = middle.list_channels(); - let middle_scids: Vec<_> = middle_chan_ids - .iter() - .map(|chan_id| { - middle_chans - .iter() - .find(|chan| chan.channel_id == *chan_id) - .and_then(|chan| chan.short_channel_id) - .unwrap() - }) - .collect(); - - let dest_chans = dest.list_channels(); - let dest_scids: Vec<_> = dest_chan_ids - .iter() - .map(|chan_id| { - dest_chans - .iter() - .find(|chan| chan.channel_id == *chan_id) - .and_then(|chan| chan.short_channel_id) - .unwrap() - }) - .collect(); - - for i in 0..num_paths { - let middle_scid = middle_scids[i % middle_scids.len()]; - let dest_scid = dest_scids[i % dest_scids.len()]; - - let path_amt = if i == num_paths - 1 { - amt - amt_per_path * (num_paths as u64 - 1) - } else { - amt_per_path - }; - let path_fee = if i == num_paths - 1 { - first_hop_fee - fee_per_path * (num_paths as u64 - 1) - } else { - fee_per_path - }; - - paths.push(Path { - hops: vec![ - RouteHop { - pubkey: middle.get_our_node_id(), - node_features: middle.node_features(), - short_channel_id: middle_scid, - channel_features: middle.channel_features(), - fee_msat: path_fee, - cltv_expiry_delta: 100, - maybe_announced_channel: true, - }, - RouteHop { - pubkey: dest.get_our_node_id(), - node_features: dest.node_features(), - short_channel_id: dest_scid, - channel_features: dest.channel_features(), - fee_msat: path_amt, - cltv_expiry_delta: 200, - maybe_announced_channel: true, - }, - ], - blinded_tail: None, - }); - } - - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::from_node_id(dest.get_our_node_id(), TEST_FINAL_CLTV), - amt, - ); - let route = Route { paths, route_params: Some(route_params) }; - let onion = RecipientOnionFields::secret_only(payment_secret, amt); - let res = source.send_payment_with_route(route, payment_hash, onion, payment_id); - match res { - Err(_) => false, - Ok(()) => check_payment_send_events(source, payment_id), - } -} - #[inline] fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) { // Since sending/receiving messages may be delayed, `timer_tick_occurred` may cause a node to @@ -1590,6 +1282,466 @@ impl PeerLink { } } +struct NodePayments { + pending: Vec, + resolved: HashMap>, +} + +impl NodePayments { + fn new() -> Self { + Self { pending: Vec::new(), resolved: new_hash_map() } + } +} + +struct PaymentTracker { + nodes: [NodePayments; 3], + claimed_payment_hashes: HashSet, + payment_preimages: HashMap, + payment_ctr: u64, +} + +impl PaymentTracker { + fn new() -> Self { + Self { + nodes: [NodePayments::new(), NodePayments::new(), NodePayments::new()], + claimed_payment_hashes: HashSet::new(), + payment_preimages: new_hash_map(), + payment_ctr: 0, + } + } + + // Returns a bool indicating whether the payment failed. + fn check_payment_send_events(source: &ChanMan, sent_payment_id: PaymentId) -> bool { + for payment in source.list_recent_payments() { + match payment { + RecentPaymentDetails::Pending { payment_id, .. } + if payment_id == sent_payment_id => + { + return true; + }, + RecentPaymentDetails::Abandoned { payment_id, .. } + if payment_id == sent_payment_id => + { + return false; + }, + _ => {}, + } + } + return false; + } + + fn next_payment(&mut self, dest: &ChanMan) -> (PaymentSecret, PaymentHash, PaymentId) { + self.payment_ctr += 1; + let mut payment_preimage = PaymentPreimage([0; 32]); + payment_preimage.0[0..8].copy_from_slice(&self.payment_ctr.to_be_bytes()); + let hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); + let secret = dest + .create_inbound_payment_for_hash(hash, None, 3600, None) + .expect("create_inbound_payment_for_hash failed"); + assert!(self.payment_preimages.insert(hash, payment_preimage).is_none()); + let mut id = PaymentId([0; 32]); + id.0[0..8].copy_from_slice(&self.payment_ctr.to_ne_bytes()); + (secret, hash, id) + } + + fn send( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, dest_idx: usize, + dest_chan_id: ChannelId, amt: u64, + ) -> bool { + let source = &nodes[source_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash, id) = self.next_payment(dest); + let (min_value_sendable, max_value_sendable, dest_scid) = source + .list_usable_channels() + .iter() + .find(|chan| chan.channel_id == dest_chan_id) + .map(|chan| { + ( + chan.next_outbound_htlc_minimum_msat, + chan.next_outbound_htlc_limit_msat, + chan.short_channel_id.unwrap_or(0), + ) + }) + .unwrap_or((0, 0, 0)); + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(source.get_our_node_id(), TEST_FINAL_CLTV), + amt, + ); + let route = Route { + paths: vec![Path { + hops: vec![RouteHop { + pubkey: dest.get_our_node_id(), + node_features: dest.node_features(), + short_channel_id: dest_scid, + channel_features: dest.channel_features(), + fee_msat: amt, + cltv_expiry_delta: 200, + maybe_announced_channel: true, + }], + blinded_tail: None, + }], + route_params: Some(route_params.clone()), + }; + let onion = RecipientOnionFields::secret_only(secret, amt); + let res = source.send_payment_with_route(route, hash, onion, id); + let succeeded = match res { + Err(err) => { + panic!("Errored with {:?} on initial payment send", err); + }, + Ok(()) => { + let expect_failure = amt < min_value_sendable || amt > max_value_sendable; + let succeeded = Self::check_payment_send_events(source, id); + assert_eq!(succeeded, !expect_failure); + succeeded + }, + }; + if succeeded { + self.nodes[source_idx].pending.push(id); + } + succeeded + } + + fn send_hop( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, middle_idx: usize, + middle_chan_id: ChannelId, dest_idx: usize, dest_chan_id: ChannelId, amt: u64, + ) { + let source = &nodes[source_idx]; + let middle = &nodes[middle_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash, id) = self.next_payment(dest); + let (min_value_sendable, max_value_sendable, middle_scid) = source + .list_usable_channels() + .iter() + .find(|chan| chan.channel_id == middle_chan_id) + .map(|chan| { + ( + chan.next_outbound_htlc_minimum_msat, + chan.next_outbound_htlc_limit_msat, + chan.short_channel_id.unwrap_or(0), + ) + }) + .unwrap_or((0, 0, 0)); + let dest_scid = dest + .list_channels() + .iter() + .find(|chan| chan.channel_id == dest_chan_id) + .and_then(|chan| chan.short_channel_id) + .unwrap_or(0); + let first_hop_fee = 50_000; + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(source.get_our_node_id(), TEST_FINAL_CLTV), + amt, + ); + let route = Route { + paths: vec![Path { + hops: vec![ + RouteHop { + pubkey: middle.get_our_node_id(), + node_features: middle.node_features(), + short_channel_id: middle_scid, + channel_features: middle.channel_features(), + fee_msat: first_hop_fee, + cltv_expiry_delta: 100, + maybe_announced_channel: true, + }, + RouteHop { + pubkey: dest.get_our_node_id(), + node_features: dest.node_features(), + short_channel_id: dest_scid, + channel_features: dest.channel_features(), + fee_msat: amt, + cltv_expiry_delta: 200, + maybe_announced_channel: true, + }, + ], + blinded_tail: None, + }], + route_params: Some(route_params.clone()), + }; + let onion = RecipientOnionFields::secret_only(secret, amt); + let res = source.send_payment_with_route(route, hash, onion, id); + let succeeded = match res { + Err(err) => { + panic!("Errored with {:?} on initial payment send", err); + }, + Ok(()) => { + let sent_amt = amt + first_hop_fee; + let expect_failure = sent_amt < min_value_sendable || sent_amt > max_value_sendable; + let succeeded = Self::check_payment_send_events(source, id); + assert_eq!(succeeded, !expect_failure); + succeeded + }, + }; + if succeeded { + self.nodes[source_idx].pending.push(id); + } + } + + fn send_noret( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, dest_idx: usize, + dest_chan_id: ChannelId, amt: u64, + ) { + self.send(nodes, source_idx, dest_idx, dest_chan_id, amt); + } + + fn send_hop_noret( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, middle_idx: usize, + middle_chan_id: ChannelId, dest_idx: usize, dest_chan_id: ChannelId, amt: u64, + ) { + self.send_hop(nodes, source_idx, middle_idx, middle_chan_id, dest_idx, dest_chan_id, amt); + } + + // Direct MPP payment (no hop) + fn send_mpp_direct( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, dest_idx: usize, + dest_chan_ids: &[ChannelId], amt: u64, + ) { + let source = &nodes[source_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash, id) = self.next_payment(dest); + let num_paths = dest_chan_ids.len(); + if num_paths == 0 { + return; + } + + let amt_per_path = amt / num_paths as u64; + let mut paths = Vec::with_capacity(num_paths); + + let dest_chans = dest.list_channels(); + let dest_scids = dest_chan_ids.iter().map(|chan_id| { + dest_chans + .iter() + .find(|chan| chan.channel_id == *chan_id) + .and_then(|chan| chan.short_channel_id) + .unwrap() + }); + + for (i, dest_scid) in dest_scids.enumerate() { + let path_amt = if i == num_paths - 1 { + amt - amt_per_path * (num_paths as u64 - 1) + } else { + amt_per_path + }; + + paths.push(Path { + hops: vec![RouteHop { + pubkey: dest.get_our_node_id(), + node_features: dest.node_features(), + short_channel_id: dest_scid, + channel_features: dest.channel_features(), + fee_msat: path_amt, + cltv_expiry_delta: 200, + maybe_announced_channel: true, + }], + blinded_tail: None, + }); + } + + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(dest.get_our_node_id(), TEST_FINAL_CLTV), + amt, + ); + let route = Route { paths, route_params: Some(route_params) }; + let onion = RecipientOnionFields::secret_only(secret, amt); + let res = source.send_payment_with_route(route, hash, onion, id); + let succeeded = match res { + Err(_) => false, + Ok(()) => Self::check_payment_send_events(source, id), + }; + if succeeded { + self.nodes[source_idx].pending.push(id); + } + } + + // MPP payment via hop - splits payment across multiple channels on either or both hops + fn send_mpp_hop( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, middle_idx: usize, + middle_chan_ids: &[ChannelId], dest_idx: usize, dest_chan_ids: &[ChannelId], amt: u64, + ) { + let source = &nodes[source_idx]; + let middle = &nodes[middle_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash, id) = self.next_payment(dest); + // Create paths by pairing middle_scids with dest_scids. + let num_paths = middle_chan_ids.len().max(dest_chan_ids.len()); + if num_paths == 0 { + return; + } + + let first_hop_fee = 50_000; + let amt_per_path = amt / num_paths as u64; + let fee_per_path = first_hop_fee / num_paths as u64; + let mut paths = Vec::with_capacity(num_paths); + + let middle_chans = middle.list_channels(); + let middle_scids: Vec<_> = middle_chan_ids + .iter() + .map(|chan_id| { + middle_chans + .iter() + .find(|chan| chan.channel_id == *chan_id) + .and_then(|chan| chan.short_channel_id) + .unwrap() + }) + .collect(); + + let dest_chans = dest.list_channels(); + let dest_scids: Vec<_> = dest_chan_ids + .iter() + .map(|chan_id| { + dest_chans + .iter() + .find(|chan| chan.channel_id == *chan_id) + .and_then(|chan| chan.short_channel_id) + .unwrap() + }) + .collect(); + + for i in 0..num_paths { + let middle_scid = middle_scids[i % middle_scids.len()]; + let dest_scid = dest_scids[i % dest_scids.len()]; + + let path_amt = if i == num_paths - 1 { + amt - amt_per_path * (num_paths as u64 - 1) + } else { + amt_per_path + }; + let path_fee = if i == num_paths - 1 { + first_hop_fee - fee_per_path * (num_paths as u64 - 1) + } else { + fee_per_path + }; + + paths.push(Path { + hops: vec![ + RouteHop { + pubkey: middle.get_our_node_id(), + node_features: middle.node_features(), + short_channel_id: middle_scid, + channel_features: middle.channel_features(), + fee_msat: path_fee, + cltv_expiry_delta: 100, + maybe_announced_channel: true, + }, + RouteHop { + pubkey: dest.get_our_node_id(), + node_features: dest.node_features(), + short_channel_id: dest_scid, + channel_features: dest.channel_features(), + fee_msat: path_amt, + cltv_expiry_delta: 200, + maybe_announced_channel: true, + }, + ], + blinded_tail: None, + }); + } + + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(dest.get_our_node_id(), TEST_FINAL_CLTV), + amt, + ); + let route = Route { paths, route_params: Some(route_params) }; + let onion = RecipientOnionFields::secret_only(secret, amt); + let res = source.send_payment_with_route(route, hash, onion, id); + let succeeded = match res { + Err(_) => false, + Ok(()) => Self::check_payment_send_events(source, id), + }; + if succeeded { + self.nodes[source_idx].pending.push(id); + } + } + + fn send_mpp_hop_noret( + &mut self, nodes: &[HarnessNode<'_>; 3], source_idx: usize, middle_idx: usize, + middle_chan_ids: &[ChannelId], dest_idx: usize, dest_chan_ids: &[ChannelId], amt: u64, + ) { + self.send_mpp_hop( + nodes, + source_idx, + middle_idx, + middle_chan_ids, + dest_idx, + dest_chan_ids, + amt, + ); + } + + fn claim_payment(&mut self, node: &HarnessNode<'_>, payment_hash: PaymentHash, fail: bool) { + if fail { + node.fail_htlc_backwards(&payment_hash); + } else { + let payment_preimage = *self + .payment_preimages + .get(&payment_hash) + .expect("PaymentClaimable for unknown payment hash"); + node.claim_funds(payment_preimage); + self.claimed_payment_hashes.insert(payment_hash); + } + } + + fn mark_sent(&mut self, node_idx: usize, sent_id: PaymentId, payment_hash: PaymentHash) { + let node = &mut self.nodes[node_idx]; + let idx_opt = node.pending.iter().position(|id| *id == sent_id); + if let Some(idx) = idx_opt { + node.pending.remove(idx); + node.resolved.insert(sent_id, Some(payment_hash)); + } else { + assert!(node.resolved.contains_key(&sent_id)); + } + } + + fn mark_resolved_without_hash(&mut self, node_idx: usize, payment_id: PaymentId) { + let node = &mut self.nodes[node_idx]; + let idx_opt = node.pending.iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + node.pending.remove(idx); + node.resolved.insert(payment_id, None); + } else if !node.resolved.contains_key(&payment_id) { + // Some resolutions can arrive immediately, before the send helper records + // the payment as pending. Track them so later duplicate events are accepted. + node.resolved.insert(payment_id, None); + } + } + + fn mark_successful_probe(&mut self, node_idx: usize, payment_id: PaymentId) { + let node = &mut self.nodes[node_idx]; + let idx_opt = node.pending.iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + node.pending.remove(idx); + node.resolved.insert(payment_id, None); + } else { + assert!(node.resolved.contains_key(&payment_id)); + } + } + + fn assert_all_resolved(&self) { + for (idx, node) in self.nodes.iter().enumerate() { + assert!( + node.pending.is_empty(), + "Node {} has {} stuck pending payments after settling all state", + idx, + node.pending.len() + ); + } + } + + fn assert_claims_reported(&self) { + for hash in self.claimed_payment_hashes.iter() { + let found = self + .nodes + .iter() + .any(|node| node.resolved.values().any(|h| h.as_ref() == Some(hash))); + assert!( + found, + "Payment {:?} was claimed by receiver but sender never got PaymentSent", + hash + ); + } + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1958,21 +2110,13 @@ pub fn do_test(data: &[u8], out: Out) { let chan_a_id = ab_link.first_channel_id(); let chan_b_id = bc_link.first_channel_id(); - let mut p_ctr: u64 = 0; - let mut queues = EventQueues::new(); + let mut payments = PaymentTracker::new(); for node in &mut nodes { node.serialized_manager = node.encode(); } - let pending_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]); - let resolved_payments: RefCell<[HashMap>; 3]> = - RefCell::new([new_hash_map(), new_hash_map(), new_hash_map()]); - let claimed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); - let payment_preimages: RefCell> = - RefCell::new(new_hash_map()); - macro_rules! test_return { () => {{ assert_test_invariants(&nodes); @@ -2246,61 +2390,26 @@ pub fn do_test(data: &[u8], out: Out) { 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, .. } => { if claim_set.insert(payment_hash.0, ()).is_none() { - if $fail { - nodes[$node].fail_htlc_backwards(&payment_hash); - } else { - let payment_preimage = *payment_preimages - .borrow() - .get(&payment_hash) - .expect("PaymentClaimable for unknown payment hash"); - nodes[$node].claim_funds(payment_preimage); - claimed_payment_hashes.borrow_mut().insert(payment_hash); - } + payments.claim_payment(&nodes[$node], payment_hash, $fail); } }, 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].insert(sent_id, Some(payment_hash)); - } else { - assert!(resolved_payments[$node].contains_key(&sent_id)); - } + payments.mark_sent($node, payment_id.unwrap(), payment_hash); }, // Even though we don't explicitly send probes, because probes are - // detected based on hashing the payment hash+preimage, its rather + // detected based on hashing the payment hash+preimage, it is rather // trivial for the fuzzer to build payments that accidentally end up // looking like probes. events::Event::ProbeSuccessful { 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].insert(payment_id, None); - } else { - assert!(resolved_payments[$node].contains_key(&payment_id)); - } + payments.mark_successful_probe($node, payment_id); }, events::Event::PaymentFailed { payment_id, .. } | events::Event::ProbeFailed { 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].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].insert(payment_id, None); - } + payments.mark_resolved_without_hash($node, payment_id); }, events::Event::PaymentClaimed { .. } => {}, events::Event::PaymentPathSuccessful { .. } => {}, @@ -2308,7 +2417,6 @@ pub fn do_test(data: &[u8], out: Out) { events::Event::PaymentForwarded { .. } if $node == 1 => {}, events::Event::ChannelReady { .. } => {}, events::Event::HTLCHandlingFailed { .. } => {}, - events::Event::FundingTransactionReadyForSigning { channel_id, counterparty_node_id, @@ -2339,8 +2447,7 @@ pub fn do_test(data: &[u8], out: Out) { | events::FundingInfo::Tx { .. }, .. } => {}, - - _ => panic!("Unhandled event: {:?}", event), + _ => panic!("Unhandled event"), } } while nodes[$node].needs_pending_htlc_processing() { @@ -2356,98 +2463,58 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - 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, &payment_preimages); - let mut id = PaymentId([0; 32]); - id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); - if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); + macro_rules! process_all_events { + () => {{ + let mut last_pass_no_updates = false; + for i in 0..std::usize::MAX { + if i == 100 { + panic!( + "It may take may iterations to settle the state, but it should not take forever" + ); + } + // Next, make sure no monitor updates are pending. + ab_link.complete_all_monitor_updates(&nodes); + bc_link.complete_all_monitor_updates(&nodes); + // 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; + continue; + } + if process_msg_events!(1, false, ProcessMessages::AllMessages) { + last_pass_no_updates = false; + continue; + } + if process_msg_events!(2, false, ProcessMessages::AllMessages) { + last_pass_no_updates = false; + continue; + } + // ...making sure any payments are claimed. + if process_events!(0, false) { + last_pass_no_updates = false; + continue; + } + if process_events!(1, false) { + last_pass_no_updates = false; + continue; + } + if process_events!(2, false) { + last_pass_no_updates = false; + continue; + } + if last_pass_no_updates { + // In some cases, we may generate a message to send in + // `process_msg_events`, but block sending until + // `complete_all_monitor_updates` gets called on the next + // iteration. + // + // Thus, we only exit if we manage two iterations with no messages + // or events to process. + break; + } + last_pass_no_updates = true; } - 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); - }; - - let send_hop_noret = |source_idx: usize, - middle_idx: usize, - middle_chan_id: ChannelId, - dest_idx: usize, - dest_chan_id: ChannelId, - 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, &payment_preimages); - let mut id = PaymentId([0; 32]); - id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - let succeeded = send_hop_payment( - source, - middle, - middle_chan_id, - dest, - dest_chan_id, - amt, - secret, - hash, - id, - ); - if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); - } - }; - - // Direct MPP payment (no hop) - let send_mpp_direct = |source_idx: usize, - dest_idx: usize, - dest_chan_ids: &[ChannelId], - amt: u64, - payment_ctr: &mut u64| { - let source = &nodes[source_idx]; - let dest = &nodes[dest_idx]; - let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); - let mut id = PaymentId([0; 32]); - id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - let succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id); - if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); - } - }; - - // MPP payment via hop - splits payment across multiple channels on either or both hops - let send_mpp_hop = |source_idx: usize, - middle_idx: usize, - middle_chan_ids: &[ChannelId], - dest_idx: usize, - dest_chan_ids: &[ChannelId], - 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, &payment_preimages); - let mut id = PaymentId([0; 32]); - id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - let succeeded = send_mpp_hop_payment( - source, - middle, - middle_chan_ids, - dest, - dest_chan_ids, - amt, - secret, - hash, - id, - ); - if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); - } - }; + }}; + } let v = get_slice!(1)[0]; out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); @@ -2519,74 +2586,104 @@ pub fn do_test(data: &[u8], out: Out) { 0x27 => process_ev_noret!(2, false), // 1/10th the channel size: - 0x30 => send_noret(0, 1, chan_a_id, 10_000_000, &mut p_ctr), - 0x31 => send_noret(1, 0, chan_a_id, 10_000_000, &mut p_ctr), - 0x32 => send_noret(1, 2, chan_b_id, 10_000_000, &mut p_ctr), - 0x33 => send_noret(2, 1, chan_b_id, 10_000_000, &mut p_ctr), - 0x34 => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 10_000_000, &mut p_ctr), - 0x35 => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 10_000_000, &mut p_ctr), - - 0x38 => send_noret(0, 1, chan_a_id, 1_000_000, &mut p_ctr), - 0x39 => send_noret(1, 0, chan_a_id, 1_000_000, &mut p_ctr), - 0x3a => send_noret(1, 2, chan_b_id, 1_000_000, &mut p_ctr), - 0x3b => send_noret(2, 1, chan_b_id, 1_000_000, &mut p_ctr), - 0x3c => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 1_000_000, &mut p_ctr), - 0x3d => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 1_000_000, &mut p_ctr), - - 0x40 => send_noret(0, 1, chan_a_id, 100_000, &mut p_ctr), - 0x41 => send_noret(1, 0, chan_a_id, 100_000, &mut p_ctr), - 0x42 => send_noret(1, 2, chan_b_id, 100_000, &mut p_ctr), - 0x43 => send_noret(2, 1, chan_b_id, 100_000, &mut p_ctr), - 0x44 => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 100_000, &mut p_ctr), - 0x45 => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 100_000, &mut p_ctr), - - 0x48 => send_noret(0, 1, chan_a_id, 10_000, &mut p_ctr), - 0x49 => send_noret(1, 0, chan_a_id, 10_000, &mut p_ctr), - 0x4a => send_noret(1, 2, chan_b_id, 10_000, &mut p_ctr), - 0x4b => send_noret(2, 1, chan_b_id, 10_000, &mut p_ctr), - 0x4c => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 10_000, &mut p_ctr), - 0x4d => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 10_000, &mut p_ctr), - - 0x50 => send_noret(0, 1, chan_a_id, 1_000, &mut p_ctr), - 0x51 => send_noret(1, 0, chan_a_id, 1_000, &mut p_ctr), - 0x52 => send_noret(1, 2, chan_b_id, 1_000, &mut p_ctr), - 0x53 => send_noret(2, 1, chan_b_id, 1_000, &mut p_ctr), - 0x54 => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 1_000, &mut p_ctr), - 0x55 => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 1_000, &mut p_ctr), - - 0x58 => send_noret(0, 1, chan_a_id, 100, &mut p_ctr), - 0x59 => send_noret(1, 0, chan_a_id, 100, &mut p_ctr), - 0x5a => send_noret(1, 2, chan_b_id, 100, &mut p_ctr), - 0x5b => send_noret(2, 1, chan_b_id, 100, &mut p_ctr), - 0x5c => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 100, &mut p_ctr), - 0x5d => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 100, &mut p_ctr), - - 0x60 => send_noret(0, 1, chan_a_id, 10, &mut p_ctr), - 0x61 => send_noret(1, 0, chan_a_id, 10, &mut p_ctr), - 0x62 => send_noret(1, 2, chan_b_id, 10, &mut p_ctr), - 0x63 => send_noret(2, 1, chan_b_id, 10, &mut p_ctr), - 0x64 => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 10, &mut p_ctr), - 0x65 => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 10, &mut p_ctr), - - 0x68 => send_noret(0, 1, chan_a_id, 1, &mut p_ctr), - 0x69 => send_noret(1, 0, chan_a_id, 1, &mut p_ctr), - 0x6a => send_noret(1, 2, chan_b_id, 1, &mut p_ctr), - 0x6b => send_noret(2, 1, chan_b_id, 1, &mut p_ctr), - 0x6c => send_hop_noret(0, 1, chan_a_id, 2, chan_b_id, 1, &mut p_ctr), - 0x6d => send_hop_noret(2, 1, chan_b_id, 0, chan_a_id, 1, &mut p_ctr), + 0x30 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10_000_000), + 0x31 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10_000_000), + 0x32 => payments.send_noret(&nodes, 1, 2, chan_b_id, 10_000_000), + 0x33 => payments.send_noret(&nodes, 2, 1, chan_b_id, 10_000_000), + 0x34 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10_000_000), + 0x35 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10_000_000), + + 0x38 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1_000_000), + 0x39 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1_000_000), + 0x3a => payments.send_noret(&nodes, 1, 2, chan_b_id, 1_000_000), + 0x3b => payments.send_noret(&nodes, 2, 1, chan_b_id, 1_000_000), + 0x3c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1_000_000), + 0x3d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1_000_000), + + 0x40 => payments.send_noret(&nodes, 0, 1, chan_a_id, 100_000), + 0x41 => payments.send_noret(&nodes, 1, 0, chan_a_id, 100_000), + 0x42 => payments.send_noret(&nodes, 1, 2, chan_b_id, 100_000), + 0x43 => payments.send_noret(&nodes, 2, 1, chan_b_id, 100_000), + 0x44 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 100_000), + 0x45 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 100_000), + + 0x48 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10_000), + 0x49 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10_000), + 0x4a => payments.send_noret(&nodes, 1, 2, chan_b_id, 10_000), + 0x4b => payments.send_noret(&nodes, 2, 1, chan_b_id, 10_000), + 0x4c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10_000), + 0x4d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10_000), + + 0x50 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1_000), + 0x51 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1_000), + 0x52 => payments.send_noret(&nodes, 1, 2, chan_b_id, 1_000), + 0x53 => payments.send_noret(&nodes, 2, 1, chan_b_id, 1_000), + 0x54 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1_000), + 0x55 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1_000), + + 0x58 => payments.send_noret(&nodes, 0, 1, chan_a_id, 100), + 0x59 => payments.send_noret(&nodes, 1, 0, chan_a_id, 100), + 0x5a => payments.send_noret(&nodes, 1, 2, chan_b_id, 100), + 0x5b => payments.send_noret(&nodes, 2, 1, chan_b_id, 100), + 0x5c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 100), + 0x5d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 100), + + 0x60 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10), + 0x61 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10), + 0x62 => payments.send_noret(&nodes, 1, 2, chan_b_id, 10), + 0x63 => payments.send_noret(&nodes, 2, 1, chan_b_id, 10), + 0x64 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10), + 0x65 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10), + + 0x68 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1), + 0x69 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1), + 0x6a => payments.send_noret(&nodes, 1, 2, chan_b_id, 1), + 0x6b => payments.send_noret(&nodes, 2, 1, chan_b_id, 1), + 0x6c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1), + 0x6d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1), // MPP payments // 0x70: direct MPP from 0 to 1 (multi A-B channels) - 0x70 => send_mpp_direct(0, 1, &chan_ab_ids, 1_000_000, &mut p_ctr), + 0x70 => payments.send_mpp_direct(&nodes, 0, 1, ab_link.channel_ids(), 1_000_000), // 0x71: MPP 0->1->2, multi channels on first hop (A-B) - 0x71 => send_mpp_hop(0, 1, &chan_ab_ids, 2, &[chan_b_id], 1_000_000, &mut p_ctr), + 0x71 => payments.send_mpp_hop_noret( + &nodes, + 0, + 1, + ab_link.channel_ids(), + 2, + &[chan_b_id], + 1_000_000, + ), // 0x72: MPP 0->1->2, multi channels on both hops (A-B and B-C) - 0x72 => send_mpp_hop(0, 1, &chan_ab_ids, 2, &chan_bc_ids, 1_000_000, &mut p_ctr), + 0x72 => payments.send_mpp_hop_noret( + &nodes, + 0, + 1, + ab_link.channel_ids(), + 2, + bc_link.channel_ids(), + 1_000_000, + ), // 0x73: MPP 0->1->2, multi channels on second hop (B-C) - 0x73 => send_mpp_hop(0, 1, &[chan_a_id], 2, &chan_bc_ids, 1_000_000, &mut p_ctr), + 0x73 => payments.send_mpp_hop_noret( + &nodes, + 0, + 1, + &[chan_a_id], + 2, + bc_link.channel_ids(), + 1_000_000, + ), // 0x74: direct MPP from 0 to 1, multi parts over single channel 0x74 => { - send_mpp_direct(0, 1, &[chan_a_id, chan_a_id, chan_a_id], 1_000_000, &mut p_ctr) + payments.send_mpp_direct( + &nodes, + 0, + 1, + &[chan_a_id, chan_a_id, chan_a_id], + 1_000_000, + ); }, 0x80 => nodes[0].bump_fee_estimate(chan_type), @@ -2824,57 +2921,6 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].signer_unblocked(None); nodes[2].signer_unblocked(None); - macro_rules! process_all_events { - () => { { - let mut last_pass_no_updates = false; - for i in 0..std::usize::MAX { - if i == 100 { - panic!("It may take may iterations to settle the state, but it should not take forever"); - } - // Next, make sure no monitor updates are pending - ab_link.complete_all_monitor_updates(&nodes); - bc_link.complete_all_monitor_updates(&nodes); - // 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; - continue; - } - if process_msg_events!(1, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - if process_msg_events!(2, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - // ...making sure any payments are claimed. - if process_events!(0, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(1, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(2, false) { - last_pass_no_updates = false; - continue; - } - if last_pass_no_updates { - // In some cases, we may generate a message to send in - // `process_msg_events`, but block sending until - // `complete_all_monitor_updates` gets called on the next - // iteration. - // - // Thus, we only exit if we manage two iterations with no messages - // or events to process. - break; - } - last_pass_no_updates = true; - } - } }; - } - process_all_events!(); // Since MPP payments are supported, we wait until we fully settle the state of all @@ -2886,40 +2932,22 @@ pub fn do_test(data: &[u8], out: Out) { process_all_events!(); // Verify no payments are stuck - all should have resolved - for (idx, pending) in pending_payments.borrow().iter().enumerate() { - assert!( - pending.is_empty(), - "Node {} has {} stuck pending payments after settling all state", - idx, - pending.len() - ); - } - + payments.assert_all_resolved(); // 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 - ); - } + payments.assert_claims_reported(); // Finally, make sure that at least one end of each channel can make a substantial payment for &chan_id in ab_link.channel_ids() { assert!( - send(0, 1, chan_id, 10_000_000, &mut p_ctr) - || send(1, 0, chan_id, 10_000_000, &mut p_ctr) + payments.send(&nodes, 0, 1, chan_id, 10_000_000) + || payments.send(&nodes, 1, 0, chan_id, 10_000_000) ); } for &chan_id in bc_link.channel_ids() { assert!( - send(1, 2, chan_id, 10_000_000, &mut p_ctr) - || send(2, 1, chan_id, 10_000_000, &mut p_ctr) + payments.send(&nodes, 1, 2, chan_id, 10_000_000) + || payments.send(&nodes, 2, 1, chan_id, 10_000_000) ); } From a05b0e8fb4cff73eeb79f7c30e287a1acbf661f7 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 1 May 2026 13:01:06 +0200 Subject: [PATCH 10/11] Route chanmon fuzz exits through loop break Replace the local test_return macro with a labeled fuzz loop. Keep one invariant check after the loop. Leave harness setup extraction for the next commit. --- fuzz/src/chanmon_consistency.rs | 49 ++++++++++++--------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index b88b36fac46..ab0082b4710 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -2082,7 +2082,7 @@ pub fn do_test(data: &[u8], out: Out) { make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions - // during normal operation in `test_return`. + // during normal operation after setup. nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); @@ -2117,26 +2117,8 @@ pub fn do_test(data: &[u8], out: Out) { node.serialized_manager = node.encode(); } - macro_rules! test_return { - () => {{ - assert_test_invariants(&nodes); - return; - }}; - } - - let mut read_pos = 1; // First byte was consumed for initial config (persistence styles + chan_type) - macro_rules! get_slice { - ($len: expr) => {{ - let slice_len = $len as usize; - if data.len() < read_pos + slice_len { - test_return!(); - } - read_pos += slice_len; - &data[read_pos - slice_len..read_pos] - }}; - } - - loop { + let mut read_pos = 1; // First byte was consumed for initial config. + 'fuzz_loop: loop { // While delivering messages, we select across three possible message selection processes // to ensure we get as much coverage as possible. See the individual enum variants for more // details. @@ -2516,7 +2498,11 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let v = get_slice!(1)[0]; + if data.len() < read_pos + 1 { + break 'fuzz_loop; + } + let v = data[read_pos]; + read_pos += 1; out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); match v { // In general, we keep related message groups close together in binary form, allowing @@ -2695,28 +2681,28 @@ pub fn do_test(data: &[u8], out: Out) { 0xa0 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[1].get_our_node_id(); nodes[0].splice_in(&cp_node_id, &chan_a_id); }, 0xa1 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[0].get_our_node_id(); nodes[1].splice_in(&cp_node_id, &chan_a_id); }, 0xa2 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[2].get_our_node_id(); nodes[1].splice_in(&cp_node_id, &chan_b_id); }, 0xa3 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[1].get_our_node_id(); nodes[2].splice_in(&cp_node_id, &chan_b_id); @@ -2724,28 +2710,28 @@ pub fn do_test(data: &[u8], out: Out) { 0xa4 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[1].get_our_node_id(); nodes[0].splice_out(&cp_node_id, &chan_a_id); }, 0xa5 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[0].get_our_node_id(); nodes[1].splice_out(&cp_node_id, &chan_a_id); }, 0xa6 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[2].get_our_node_id(); nodes[1].splice_out(&cp_node_id, &chan_b_id); }, 0xa7 => { if !cfg!(splicing) { - test_return!(); + break 'fuzz_loop; } let cp_node_id = nodes[1].get_our_node_id(); nodes[2].splice_out(&cp_node_id, &chan_b_id); @@ -2955,13 +2941,14 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].record_last_htlc_clear_fee(); nodes[2].record_last_htlc_clear_fee(); }, - _ => test_return!(), + _ => break 'fuzz_loop, } for node in &mut nodes { node.refresh_serialized_manager(); } } + assert_test_invariants(&nodes); } pub fn chanmon_consistency_test(data: &[u8], out: Out) { From 0d340f8de0c6995dd6b0ef9cbe0009277428bde6 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 1 May 2026 13:35:45 +0200 Subject: [PATCH 11/11] Build chanmon consistency harness Collect the chanmon consistency setup, state, and main fuzz flow into a harness. Keep do_test focused on reading fuzz bytes and dispatching actions. --- fuzz/src/chanmon_consistency.rs | 1866 +++++++++++++++++-------------- 1 file changed, 1056 insertions(+), 810 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index ab0082b4710..20ee98c0411 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -634,6 +634,21 @@ enum ChanType { ZeroFeeCommitments, } +// While delivering messages, select across three possible message selection +// processes to maximize coverage. See the individual enum variants for details. +#[derive(Copy, Clone, PartialEq, Eq)] +enum ProcessMessages { + /// Deliver all available messages, including fetching any new messages from + /// `get_and_clear_pending_msg_events()` which may have side effects. + AllMessages, + /// Call `get_and_clear_pending_msg_events()` first, then deliver up to one + /// message, which may already be queued. + OneMessage, + /// Deliver up to one already-queued message. This avoids the side effects of + /// `get_and_clear_pending_msg_events()`, such as freeing the HTLC holding cell. + OnePendingMessage, +} + struct HarnessNode<'a> { node_id: u8, node: ChanMan<'a>, @@ -1013,6 +1028,19 @@ enum MonitorUpdateSelector { Last, } +#[derive(Copy, Clone)] +enum MppDirectChannels { + All, + RepeatedFirst, +} + +#[derive(Copy, Clone)] +enum MppHopChannels { + FirstHop, + BothHops, + SecondHop, +} + struct EventQueues { ab: Vec, ba: Vec, @@ -1199,6 +1227,11 @@ impl PeerLink { &self.channel_ids } + fn connects(&self, node_a: usize, node_b: usize) -> bool { + (self.node_a == node_a && self.node_b == node_b) + || (self.node_a == node_b && self.node_b == node_a) + } + fn complete_all_monitor_updates(&self, nodes: &[HarnessNode<'_>; 3]) { for id in &self.channel_ids { nodes[self.node_a].complete_all_monitor_updates(id); @@ -1742,6 +1775,17 @@ impl PaymentTracker { } } +struct Harness<'a, Out: Output + MaybeSend + MaybeSync> { + out: Out, + chan_type: ChanType, + chain_state: ChainState, + nodes: [HarnessNode<'a>; 3], + ab_link: PeerLink, + bc_link: PeerLink, + queues: EventQueues, + payments: PaymentTracker, +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1967,988 +2011,1190 @@ fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { } } -#[inline] -pub fn do_test(data: &[u8], out: Out) { - let router = FuzzRouter {}; +impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { + fn new(config_byte: u8, out: Out, router: &'a FuzzRouter) -> Self { + let chan_type = match (config_byte >> 3) & 0b11 { + 0 => ChanType::Legacy, + 1 => ChanType::KeyedAnchors, + _ => ChanType::ZeroFeeCommitments, + }; + let persistence_styles = [ + if config_byte & 0b01 != 0 { + ChannelMonitorUpdateStatus::InProgress + } else { + ChannelMonitorUpdateStatus::Completed + }, + if config_byte & 0b10 != 0 { + ChannelMonitorUpdateStatus::InProgress + } else { + ChannelMonitorUpdateStatus::Completed + }, + if config_byte & 0b100 != 0 { + ChannelMonitorUpdateStatus::InProgress + } else { + ChannelMonitorUpdateStatus::Completed + }, + ]; + + let wallet_a = TestWalletSource::new(SecretKey::from_slice(&[1; 32]).unwrap()); + let wallet_b = TestWalletSource::new(SecretKey::from_slice(&[2; 32]).unwrap()); + let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); + let wallets = [&wallet_a, &wallet_b, &wallet_c]; + let coinbase_tx = bitcoin::Transaction { + version: bitcoin::transaction::Version::TWO, + lock_time: bitcoin::absolute::LockTime::ZERO, + input: vec![bitcoin::TxIn { ..Default::default() }], + output: wallets + .iter() + .map(|wallet| TxOut { + value: Amount::from_sat(100_000), + script_pubkey: wallet.get_change_script().unwrap(), + }) + .collect(), + }; + for (idx, wallet) in wallets.iter().enumerate() { + wallet.add_utxo(coinbase_tx.clone(), idx as u32); + } - // Read initial monitor styles and channel type from fuzz input byte 0: - // bits 0-2: monitor styles (1 bit per node) - // bits 3-4: channel type (0=Legacy, 1=KeyedAnchors, 2=ZeroFeeCommitments) - let config_byte = if !data.is_empty() { data[0] } else { 0 }; - let chan_type = match (config_byte >> 3) & 0b11 { - 0 => ChanType::Legacy, - 1 => ChanType::KeyedAnchors, - _ => ChanType::ZeroFeeCommitments, - }; - let persistence_styles = [ - if config_byte & 0b01 != 0 { - ChannelMonitorUpdateStatus::InProgress - } else { - ChannelMonitorUpdateStatus::Completed - }, - if config_byte & 0b10 != 0 { - ChannelMonitorUpdateStatus::InProgress - } else { - ChannelMonitorUpdateStatus::Completed - }, - if config_byte & 0b100 != 0 { - ChannelMonitorUpdateStatus::InProgress - } else { - ChannelMonitorUpdateStatus::Completed - }, - ]; - - let mut chain_state = ChainState::new(); - let wallet_a = TestWalletSource::new(SecretKey::from_slice(&[1; 32]).unwrap()); - let wallet_b = TestWalletSource::new(SecretKey::from_slice(&[2; 32]).unwrap()); - let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); - - let wallets = [&wallet_a, &wallet_b, &wallet_c]; - let coinbase_tx = bitcoin::Transaction { - version: bitcoin::transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![bitcoin::TxIn { ..Default::default() }], - output: wallets - .iter() - .map(|wallet| TxOut { - value: Amount::from_sat(100_000), - script_pubkey: wallet.get_change_script().unwrap(), - }) - .collect(), - }; - for (idx, wallet) in wallets.iter().enumerate() { - wallet.add_utxo(coinbase_tx.clone(), idx as u32); - } - - let fee_est_a = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let fee_est_b = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let fee_est_c = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); - let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); - let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); - let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); - - // 3 nodes is enough to hit all the possible cases, notably unknown-source-unknown-dest - // forwarding. - let mut nodes = [ - HarnessNode::new( - 0, - wallet_a, - Arc::clone(&fee_est_a), - Arc::clone(&broadcast_a), - persistence_styles[0], - &out, - &router, - chan_type, - ), - HarnessNode::new( - 1, - wallet_b, - Arc::clone(&fee_est_b), - Arc::clone(&broadcast_b), - persistence_styles[1], - &out, - &router, - chan_type, - ), - HarnessNode::new( - 2, - wallet_c, - Arc::clone(&fee_est_c), - Arc::clone(&broadcast_c), - persistence_styles[2], - &out, - &router, + let fee_est_a = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); + let fee_est_b = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); + let fee_est_c = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); + let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); + let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); + let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }); + + // 3 nodes is enough to hit all the possible cases, notably + // unknown-source-unknown-dest forwarding. + let mut nodes = [ + HarnessNode::new( + 0, + wallet_a, + Arc::clone(&fee_est_a), + Arc::clone(&broadcast_a), + persistence_styles[0], + &out, + router, + chan_type, + ), + HarnessNode::new( + 1, + wallet_b, + Arc::clone(&fee_est_b), + Arc::clone(&broadcast_b), + persistence_styles[1], + &out, + router, + chan_type, + ), + HarnessNode::new( + 2, + wallet_c, + Arc::clone(&fee_est_c), + Arc::clone(&broadcast_c), + persistence_styles[2], + &out, + router, + chan_type, + ), + ]; + let mut chain_state = ChainState::new(); + + // 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 distinct version numbers for each funding transaction so each test + // channel gets its own txid and funding outpoint. + // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), + // channel 3 A has 0-reserve (trusted accept). + make_channel(&nodes[0], &nodes[1], 1, false, false, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 2, true, true, &mut chain_state); + make_channel(&nodes[0], &nodes[1], 3, false, true, &mut chain_state); + // B-C: channel 4 B has 0-reserve (via trusted accept), + // channel 5 C has 0-reserve (via trusted open). + make_channel(&nodes[1], &nodes[2], 4, false, true, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 5, true, false, &mut chain_state); + make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); + + // Wipe the transactions-broadcasted set to make sure we don't broadcast + // any transactions during normal operation after setup. + nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); + + // Sync all nodes to tip to lock the funding. + nodes[0].sync_with_chain_state(&chain_state, None); + nodes[1].sync_with_chain_state(&chain_state, None); + nodes[2].sync_with_chain_state(&chain_state, None); + + lock_fundings(&nodes); + + let chan_ab_ids = { + // Get channel IDs for all A-B channels (from node A's perspective). + let node_a_chans = nodes[0].list_usable_channels(); + [node_a_chans[0].channel_id, node_a_chans[1].channel_id, node_a_chans[2].channel_id] + }; + let chan_bc_ids = { + // Get channel IDs for all B-C channels (from node C's perspective). + let node_c_chans = nodes[2].list_usable_channels(); + [node_c_chans[0].channel_id, node_c_chans[1].channel_id, node_c_chans[2].channel_id] + }; + + for node in &mut nodes { + node.serialized_manager = node.encode(); + } + + Self { + out, chan_type, - ), - ]; - - // 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 distinct version numbers for each funding transaction so each test channel gets its own - // txid and funding outpoint. - // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), - // channel 3 A has 0-reserve (trusted accept) - make_channel(&nodes[0], &nodes[1], 1, false, false, &mut chain_state); - make_channel(&nodes[0], &nodes[1], 2, true, true, &mut chain_state); - make_channel(&nodes[0], &nodes[1], 3, false, true, &mut chain_state); - // B-C: channel 4 B has 0-reserve (via trusted accept), - // channel 5 C has 0-reserve (via trusted open) - make_channel(&nodes[1], &nodes[2], 4, false, true, &mut chain_state); - make_channel(&nodes[1], &nodes[2], 5, true, false, &mut chain_state); - make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); - - // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions - // during normal operation after setup. - nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); - nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); - nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); - - // Sync all nodes to tip to lock the funding. - nodes[0].sync_with_chain_state(&chain_state, None); - nodes[1].sync_with_chain_state(&chain_state, None); - nodes[2].sync_with_chain_state(&chain_state, None); - - lock_fundings(&nodes); - - // Get channel IDs for all A-B channels (from node A's perspective) - let chan_ab_ids = { - let node_a_chans = nodes[0].list_usable_channels(); - [node_a_chans[0].channel_id, node_a_chans[1].channel_id, node_a_chans[2].channel_id] - }; - // Get channel IDs for all B-C channels (from node C's perspective) - let chan_bc_ids = { - let node_c_chans = nodes[2].list_usable_channels(); - [node_c_chans[0].channel_id, node_c_chans[1].channel_id, node_c_chans[2].channel_id] - }; - let mut ab_link = PeerLink::new(0, 1, chan_ab_ids); - let mut bc_link = PeerLink::new(1, 2, chan_bc_ids); - // Keep old names for backward compatibility in existing code - let chan_a_id = ab_link.first_channel_id(); - let chan_b_id = bc_link.first_channel_id(); + chain_state, + nodes, + ab_link: PeerLink::new(0, 1, chan_ab_ids), + bc_link: PeerLink::new(1, 2, chan_bc_ids), + queues: EventQueues::new(), + payments: PaymentTracker::new(), + } + } - let mut queues = EventQueues::new(); - let mut payments = PaymentTracker::new(); + fn chan_a_id(&self) -> ChannelId { + self.ab_link.first_channel_id() + } - for node in &mut nodes { - node.serialized_manager = node.encode(); + fn chan_b_id(&self) -> ChannelId { + self.bc_link.first_channel_id() } - let mut read_pos = 1; // First byte was consumed for initial config. - 'fuzz_loop: loop { - // While delivering messages, we select across three possible message selection processes - // to ensure we get as much coverage as possible. See the individual enum variants for more - // details. - #[derive(PartialEq)] - enum ProcessMessages { - /// Deliver all available messages, including fetching any new messages from - /// `get_and_clear_pending_msg_events()` (which may have side effects). - AllMessages, - /// Call `get_and_clear_pending_msg_events()` first, and then deliver up to one - /// message (which may already be queued). - OneMessage, - /// Deliver up to one already-queued message. This avoids any potential side-effects - /// of `get_and_clear_pending_msg_events()` (eg freeing the HTLC holding cell), which - /// provides potentially more coverage. - OnePendingMessage, - } - - macro_rules! process_msg_events { - ($node: expr, $corrupt_forward: expr, $limit_events: expr) => { { - let mut events = queues.take_for_node($node); - let mut new_events = Vec::new(); - if $limit_events != ProcessMessages::OnePendingMessage { - new_events = nodes[$node].get_and_clear_pending_msg_events(); - } - let mut had_events = false; - let mut events_iter = events.drain(..).chain(new_events.drain(..)); - let mut extra_ev = None; - for event in &mut events_iter { - had_events = true; - match event { - MessageSendEvent::UpdateHTLCs { node_id, channel_id, updates: CommitmentUpdate { update_add_htlcs, update_fail_htlcs, update_fulfill_htlcs, update_fail_malformed_htlcs, update_fee, commitment_signed } } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == node_id { - for update_add in update_add_htlcs.iter() { - out.locked_write(format!("Delivering update_add_htlc from node {} to node {}.\n", $node, idx).as_bytes()); - if !$corrupt_forward { - dest.handle_update_add_htlc(nodes[$node].get_our_node_id(), update_add); - } else { - // Corrupt the update_add_htlc message so that its HMAC - // check will fail and we generate a - // update_fail_malformed_htlc instead of an - // update_fail_htlc as we do when we reject a payment. - let mut msg_ser = update_add.encode(); - msg_ser[1000] ^= 0xff; - let new_msg = UpdateAddHTLC::read_from_fixed_length_buffer(&mut &msg_ser[..]).unwrap(); - dest.handle_update_add_htlc(nodes[$node].get_our_node_id(), &new_msg); - } - } - let processed_change = !update_add_htlcs.is_empty() || !update_fulfill_htlcs.is_empty() || - !update_fail_htlcs.is_empty() || !update_fail_malformed_htlcs.is_empty(); - for update_fulfill in update_fulfill_htlcs { - out.locked_write(format!("Delivering update_fulfill_htlc from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_update_fulfill_htlc(nodes[$node].get_our_node_id(), update_fulfill); - } - for update_fail in update_fail_htlcs.iter() { - out.locked_write(format!("Delivering update_fail_htlc from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_update_fail_htlc(nodes[$node].get_our_node_id(), update_fail); - } - for update_fail_malformed in update_fail_malformed_htlcs.iter() { - out.locked_write(format!("Delivering update_fail_malformed_htlc from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_update_fail_malformed_htlc(nodes[$node].get_our_node_id(), update_fail_malformed); - } - if let Some(msg) = update_fee { - out.locked_write(format!("Delivering update_fee from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_update_fee(nodes[$node].get_our_node_id(), &msg); - } - if $limit_events != ProcessMessages::AllMessages && processed_change { - // If we only want to process some messages, don't deliver the CS until later. - extra_ev = Some(MessageSendEvent::UpdateHTLCs { node_id, channel_id, updates: CommitmentUpdate { - update_add_htlcs: Vec::new(), - update_fail_htlcs: Vec::new(), - update_fulfill_htlcs: Vec::new(), - update_fail_malformed_htlcs: Vec::new(), - update_fee: None, - commitment_signed - } }); - break; - } - out.locked_write(format!("Delivering commitment_signed from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_commitment_signed_batch_test(nodes[$node].get_our_node_id(), &commitment_signed); - break; - } - } - }, - MessageSendEvent::SendRevokeAndACK { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering revoke_and_ack from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_revoke_and_ack(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering channel_reestablish from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_channel_reestablish(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendStfu { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering stfu from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_stfu(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxAddInput { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_add_input from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_add_input(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxAddOutput { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_add_output from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_add_output(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxRemoveInput { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_remove_input from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_remove_input(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxRemoveOutput { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_remove_output from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_remove_output(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxComplete { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_complete from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_complete(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxAbort { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_abort from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_abort(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxInitRbf { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_init_rbf from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_init_rbf(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxAckRbf { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_ack_rbf from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_ack_rbf(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendTxSignatures { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering tx_signatures from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_tx_signatures(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendSpliceInit { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering splice_init from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_splice_init(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendSpliceAck { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering splice_ack from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_splice_ack(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::SendSpliceLocked { ref node_id, ref msg } => { - for (idx, dest) in nodes.iter().enumerate() { - if dest.get_our_node_id() == *node_id { - out.locked_write(format!("Delivering splice_locked from node {} to node {}.\n", $node, idx).as_bytes()); - dest.handle_splice_locked(nodes[$node].get_our_node_id(), msg); - } - } - }, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); - }, - MessageSendEvent::SendChannelReady { .. } => { - // Can be generated as a reestablish response - }, - MessageSendEvent::SendAnnouncementSignatures { .. } => { - // Can be generated as a reestablish response - }, - MessageSendEvent::SendChannelUpdate { .. } => { - // Can be generated as a reestablish response - }, - MessageSendEvent::BroadcastChannelUpdate { .. } => { - // Can be generated as a result of calling `timer_tick_occurred` enough - // times while peers are disconnected - }, - _ => panic!("Unhandled message event {:?}", event), - } - if $limit_events != ProcessMessages::AllMessages { - break; - } - } - if $node == 1 { - let remaining = extra_ev.into_iter().chain(events_iter).collect::>(); - queues.route_from_middle(remaining, None, &nodes); - } else if $node == 0 { - if let Some(ev) = extra_ev { queues.push_for_node(0, ev); } - queues.extend_for_node(0, events_iter); - } else { - if let Some(ev) = extra_ev { queues.push_for_node(2, ev); } - queues.extend_for_node(2, events_iter); - } - had_events - } } + fn finish(&self) { + assert_test_invariants(&self.nodes); + } + + fn link_between(&self, source_idx: usize, dest_idx: usize) -> &PeerLink { + if self.ab_link.connects(source_idx, dest_idx) { + &self.ab_link + } else if self.bc_link.connects(source_idx, dest_idx) { + &self.bc_link + } else { + panic!("invalid payment peers") } + } - macro_rules! process_msg_noret { - ($node: expr, $corrupt_forward: expr, $limit_events: expr) => {{ - process_msg_events!($node, $corrupt_forward, $limit_events); - }}; + fn channel_ids_between(&self, source_idx: usize, dest_idx: usize) -> [ChannelId; 3] { + self.link_between(source_idx, dest_idx).channel_ids().clone() + } + + fn first_channel_id_between(&self, source_idx: usize, dest_idx: usize) -> ChannelId { + self.link_between(source_idx, dest_idx).first_channel_id() + } + + fn send_on_channel( + &mut self, source_idx: usize, dest_idx: usize, dest_chan_id: ChannelId, amt: u64, + ) -> bool { + self.payments.send(&self.nodes, source_idx, dest_idx, dest_chan_id, amt) + } + + fn send(&mut self, source_idx: usize, dest_idx: usize, amt: u64) { + let dest_chan_id = self.first_channel_id_between(source_idx, dest_idx); + self.payments.send_noret(&self.nodes, source_idx, dest_idx, dest_chan_id, amt); + } + + fn send_hop(&mut self, source_idx: usize, middle_idx: usize, dest_idx: usize, amt: u64) { + let middle_chan_id = self.first_channel_id_between(source_idx, middle_idx); + let dest_chan_id = self.first_channel_id_between(middle_idx, dest_idx); + self.payments.send_hop_noret( + &self.nodes, + source_idx, + middle_idx, + middle_chan_id, + dest_idx, + dest_chan_id, + amt, + ); + } + + fn send_mpp_direct( + &mut self, source_idx: usize, dest_idx: usize, channels: MppDirectChannels, amt: u64, + ) { + match channels { + MppDirectChannels::All => { + let dest_chan_ids = self.channel_ids_between(source_idx, dest_idx); + self.payments.send_mpp_direct( + &self.nodes, + source_idx, + dest_idx, + &dest_chan_ids, + amt, + ); + }, + MppDirectChannels::RepeatedFirst => { + let dest_chan_id = self.first_channel_id_between(source_idx, dest_idx); + let dest_chan_ids = [dest_chan_id, dest_chan_id, dest_chan_id]; + self.payments.send_mpp_direct( + &self.nodes, + source_idx, + dest_idx, + &dest_chan_ids, + amt, + ); + }, } + } - macro_rules! process_events { - ($node: expr, $fail: expr) => {{ - // Multiple HTLCs can resolve for the same payment hash, so deduplicate - // claim/fail handling per event batch. - let mut claim_set = new_hash_map(); - let mut events = nodes[$node].get_and_clear_pending_events(); - let had_events = !events.is_empty(); - for event in events.drain(..) { - match event { - events::Event::PaymentClaimable { payment_hash, .. } => { - if claim_set.insert(payment_hash.0, ()).is_none() { - payments.claim_payment(&nodes[$node], payment_hash, $fail); - } - }, - events::Event::PaymentSent { payment_id, payment_hash, .. } => { - payments.mark_sent($node, payment_id.unwrap(), payment_hash); - }, - // Even though we don't explicitly send probes, because probes are - // detected based on hashing the payment hash+preimage, it is rather - // trivial for the fuzzer to build payments that accidentally end up - // looking like probes. - events::Event::ProbeSuccessful { payment_id, .. } => { - payments.mark_successful_probe($node, payment_id); - }, - events::Event::PaymentFailed { payment_id, .. } - | events::Event::ProbeFailed { payment_id, .. } => { - payments.mark_resolved_without_hash($node, payment_id); - }, - events::Event::PaymentClaimed { .. } => {}, - events::Event::PaymentPathSuccessful { .. } => {}, - events::Event::PaymentPathFailed { .. } => {}, - events::Event::PaymentForwarded { .. } if $node == 1 => {}, - events::Event::ChannelReady { .. } => {}, - events::Event::HTLCHandlingFailed { .. } => {}, - events::Event::FundingTransactionReadyForSigning { - channel_id, - counterparty_node_id, - unsigned_transaction, - .. - } => { - let signed_tx = - nodes[$node].wallet.sign_tx(unsigned_transaction).unwrap(); - nodes[$node] - .funding_transaction_signed( - &channel_id, - &counterparty_node_id, - signed_tx, - ) - .unwrap(); - }, - events::Event::SplicePending { new_funding_txo, .. } => { - let mut txs = nodes[$node].broadcaster.txn_broadcasted.borrow_mut(); - assert!(txs.len() >= 1); - let splice_tx = txs.remove(0); - assert_eq!(new_funding_txo.txid, splice_tx.compute_txid()); - chain_state.add_pending_tx(splice_tx); - }, - events::Event::SpliceFailed { .. } => {}, - events::Event::DiscardFunding { - funding_info: - events::FundingInfo::Contribution { .. } - | events::FundingInfo::Tx { .. }, - .. - } => {}, - _ => panic!("Unhandled event"), - } - } - while nodes[$node].needs_pending_htlc_processing() { - nodes[$node].process_pending_htlc_forwards(); - } - had_events - }}; + fn send_mpp_hop( + &mut self, source_idx: usize, middle_idx: usize, dest_idx: usize, channels: MppHopChannels, + amt: u64, + ) { + let middle_chan_ids = self.channel_ids_between(source_idx, middle_idx); + let dest_chan_ids = self.channel_ids_between(middle_idx, dest_idx); + let middle_first_chan_id = middle_chan_ids[0]; + let dest_first_chan_id = dest_chan_ids[0]; + match channels { + MppHopChannels::FirstHop => { + let dest_chan_ids = [dest_first_chan_id]; + self.payments.send_mpp_hop_noret( + &self.nodes, + source_idx, + middle_idx, + &middle_chan_ids, + dest_idx, + &dest_chan_ids, + amt, + ); + }, + MppHopChannels::BothHops => { + self.payments.send_mpp_hop_noret( + &self.nodes, + source_idx, + middle_idx, + &middle_chan_ids, + dest_idx, + &dest_chan_ids, + amt, + ); + }, + MppHopChannels::SecondHop => { + let middle_chan_ids = [middle_first_chan_id]; + self.payments.send_mpp_hop_noret( + &self.nodes, + source_idx, + middle_idx, + &middle_chan_ids, + dest_idx, + &dest_chan_ids, + amt, + ); + }, } + } - macro_rules! process_ev_noret { - ($node: expr, $fail: expr) => {{ - process_events!($node, $fail); - }}; + fn process_msg_events( + &mut self, node_idx: usize, corrupt_forward: bool, limit_events: ProcessMessages, + ) -> bool { + fn find_destination_node(nodes: &[HarnessNode<'_>; 3], node_id: &PublicKey) -> usize { + nodes + .iter() + .position(|node| node.get_our_node_id() == *node_id) + .expect("message destination should be a known harness node") } - macro_rules! process_all_events { - () => {{ - let mut last_pass_no_updates = false; - for i in 0..std::usize::MAX { - if i == 100 { - panic!( - "It may take may iterations to settle the state, but it should not take forever" - ); - } - // Next, make sure no monitor updates are pending. - ab_link.complete_all_monitor_updates(&nodes); - bc_link.complete_all_monitor_updates(&nodes); - // 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; - continue; - } - if process_msg_events!(1, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - if process_msg_events!(2, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - // ...making sure any payments are claimed. - if process_events!(0, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(1, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(2, false) { - last_pass_no_updates = false; - continue; - } - if last_pass_no_updates { - // In some cases, we may generate a message to send in - // `process_msg_events`, but block sending until - // `complete_all_monitor_updates` gets called on the next - // iteration. - // - // Thus, we only exit if we manage two iterations with no messages - // or events to process. - break; + fn log_msg_delivery( + node_idx: usize, dest_idx: usize, msg_name: &str, out: &Out, + ) { + out.locked_write( + format!("Delivering {} from node {} to node {}.\n", msg_name, node_idx, dest_idx) + .as_bytes(), + ); + } + + fn log_peer_message( + node_idx: usize, node_id: &PublicKey, nodes: &[HarnessNode<'_>; 3], out: &Out, + msg_name: &str, + ) -> usize { + let dest_idx = find_destination_node(nodes, node_id); + log_msg_delivery(node_idx, dest_idx, msg_name, out); + dest_idx + } + + fn handle_update_add_htlc( + source_node_id: PublicKey, dest: &HarnessNode<'_>, update_add: &UpdateAddHTLC, + corrupt_forward: bool, + ) { + if !corrupt_forward { + dest.handle_update_add_htlc(source_node_id, update_add); + } else { + // Corrupt the update_add_htlc message so that its HMAC check will fail and we + // generate an update_fail_malformed_htlc instead of an update_fail_htlc as we do + // when we reject a payment. + let mut msg_ser = update_add.encode(); + msg_ser[1000] ^= 0xff; + let new_msg = + UpdateAddHTLC::read_from_fixed_length_buffer(&mut &msg_ser[..]).unwrap(); + dest.handle_update_add_htlc(source_node_id, &new_msg); + } + } + + fn handle_update_htlcs_event( + node_idx: usize, source_node_id: PublicKey, node_id: PublicKey, channel_id: ChannelId, + updates: CommitmentUpdate, corrupt_forward: bool, limit_events: ProcessMessages, + nodes: &[HarnessNode<'_>; 3], out: &Out, + ) -> Option { + let dest_idx = find_destination_node(nodes, &node_id); + let dest = &nodes[dest_idx]; + let CommitmentUpdate { + update_add_htlcs, + update_fail_htlcs, + update_fulfill_htlcs, + update_fail_malformed_htlcs, + update_fee, + commitment_signed, + } = updates; + + for update_add in update_add_htlcs.iter() { + log_msg_delivery(node_idx, dest_idx, "update_add_htlc", out); + handle_update_add_htlc(source_node_id, dest, update_add, corrupt_forward); + } + let processed_change = !update_add_htlcs.is_empty() + || !update_fulfill_htlcs.is_empty() + || !update_fail_htlcs.is_empty() + || !update_fail_malformed_htlcs.is_empty(); + for update_fulfill in update_fulfill_htlcs { + log_msg_delivery(node_idx, dest_idx, "update_fulfill_htlc", out); + dest.handle_update_fulfill_htlc(source_node_id, update_fulfill); + } + for update_fail in update_fail_htlcs.iter() { + log_msg_delivery(node_idx, dest_idx, "update_fail_htlc", out); + dest.handle_update_fail_htlc(source_node_id, update_fail); + } + for update_fail_malformed in update_fail_malformed_htlcs.iter() { + log_msg_delivery(node_idx, dest_idx, "update_fail_malformed_htlc", out); + dest.handle_update_fail_malformed_htlc(source_node_id, update_fail_malformed); + } + if let Some(msg) = update_fee { + log_msg_delivery(node_idx, dest_idx, "update_fee", out); + dest.handle_update_fee(source_node_id, &msg); + } + if limit_events != ProcessMessages::AllMessages && processed_change { + // If we only want to process some messages, don't deliver the CS until later. + return Some(MessageSendEvent::UpdateHTLCs { + node_id, + channel_id, + updates: CommitmentUpdate { + update_add_htlcs: Vec::new(), + update_fail_htlcs: Vec::new(), + update_fulfill_htlcs: Vec::new(), + update_fail_malformed_htlcs: Vec::new(), + update_fee: None, + commitment_signed, + }, + }); + } + log_msg_delivery(node_idx, dest_idx, "commitment_signed", out); + dest.handle_commitment_signed_batch_test(source_node_id, &commitment_signed); + None + } + + fn process_msg_event( + node_idx: usize, source_node_id: PublicKey, event: MessageSendEvent, + corrupt_forward: bool, limit_events: ProcessMessages, nodes: &[HarnessNode<'_>; 3], + out: &Out, + ) -> Option { + match event { + MessageSendEvent::UpdateHTLCs { node_id, channel_id, updates } => { + handle_update_htlcs_event( + node_idx, + source_node_id, + node_id, + channel_id, + updates, + corrupt_forward, + limit_events, + nodes, + out, + ) + }, + MessageSendEvent::SendRevokeAndACK { ref node_id, ref msg } => { + let dest_idx = + log_peer_message(node_idx, node_id, nodes, out, "revoke_and_ack"); + nodes[dest_idx].handle_revoke_and_ack(source_node_id, msg); + None + }, + MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } => { + let dest_idx = + log_peer_message(node_idx, node_id, nodes, out, "channel_reestablish"); + nodes[dest_idx].handle_channel_reestablish(source_node_id, msg); + None + }, + MessageSendEvent::SendStfu { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "stfu"); + nodes[dest_idx].handle_stfu(source_node_id, msg); + None + }, + MessageSendEvent::SendTxAddInput { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_add_input"); + nodes[dest_idx].handle_tx_add_input(source_node_id, msg); + None + }, + MessageSendEvent::SendTxAddOutput { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_add_output"); + nodes[dest_idx].handle_tx_add_output(source_node_id, msg); + None + }, + MessageSendEvent::SendTxRemoveInput { ref node_id, ref msg } => { + let dest_idx = + log_peer_message(node_idx, node_id, nodes, out, "tx_remove_input"); + nodes[dest_idx].handle_tx_remove_input(source_node_id, msg); + None + }, + MessageSendEvent::SendTxRemoveOutput { ref node_id, ref msg } => { + let dest_idx = + log_peer_message(node_idx, node_id, nodes, out, "tx_remove_output"); + nodes[dest_idx].handle_tx_remove_output(source_node_id, msg); + None + }, + MessageSendEvent::SendTxComplete { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_complete"); + nodes[dest_idx].handle_tx_complete(source_node_id, msg); + None + }, + MessageSendEvent::SendTxAbort { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_abort"); + nodes[dest_idx].handle_tx_abort(source_node_id, msg); + None + }, + MessageSendEvent::SendTxInitRbf { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_init_rbf"); + nodes[dest_idx].handle_tx_init_rbf(source_node_id, msg); + None + }, + MessageSendEvent::SendTxAckRbf { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_ack_rbf"); + nodes[dest_idx].handle_tx_ack_rbf(source_node_id, msg); + None + }, + MessageSendEvent::SendTxSignatures { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "tx_signatures"); + nodes[dest_idx].handle_tx_signatures(source_node_id, msg); + None + }, + MessageSendEvent::SendSpliceInit { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "splice_init"); + nodes[dest_idx].handle_splice_init(source_node_id, msg); + None + }, + MessageSendEvent::SendSpliceAck { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "splice_ack"); + nodes[dest_idx].handle_splice_ack(source_node_id, msg); + None + }, + MessageSendEvent::SendSpliceLocked { ref node_id, ref msg } => { + let dest_idx = log_peer_message(node_idx, node_id, nodes, out, "splice_locked"); + nodes[dest_idx].handle_splice_locked(source_node_id, msg); + None + }, + MessageSendEvent::HandleError { ref action, .. } => { + assert_action_timeout_awaiting_response(action); + None + }, + MessageSendEvent::SendChannelReady { .. } + | MessageSendEvent::SendAnnouncementSignatures { .. } + | MessageSendEvent::SendChannelUpdate { .. } => { + // Can be generated as a reestablish response. + None + }, + MessageSendEvent::BroadcastChannelUpdate { .. } => { + // Can be generated as a result of calling `timer_tick_occurred` enough + // times while peers are disconnected. + None + }, + _ => panic!("Unhandled message event {:?}", event), + } + } + + let nodes = &self.nodes; + let out = &self.out; + let queues = &mut self.queues; + let mut events = queues.take_for_node(node_idx); + let mut new_events = Vec::new(); + if limit_events != ProcessMessages::OnePendingMessage { + new_events = nodes[node_idx].get_and_clear_pending_msg_events(); + } + let mut had_events = false; + let source_node_id = nodes[node_idx].get_our_node_id(); + let mut events_iter = events.drain(..).chain(new_events.drain(..)); + let mut extra_ev = None; + for event in &mut events_iter { + had_events = true; + extra_ev = process_msg_event( + node_idx, + source_node_id, + event, + corrupt_forward, + limit_events, + nodes, + out, + ); + if limit_events != ProcessMessages::AllMessages { + break; + } + } + if node_idx == 1 { + let remaining = extra_ev.into_iter().chain(events_iter).collect::>(); + queues.route_from_middle(remaining, None, nodes); + } else if node_idx == 0 { + if let Some(ev) = extra_ev { + queues.push_for_node(0, ev); + } + queues.extend_for_node(0, events_iter); + } else { + if let Some(ev) = extra_ev { + queues.push_for_node(2, ev); + } + queues.extend_for_node(2, events_iter); + } + had_events + } + + fn process_events(&mut self, node_idx: usize, fail: bool) -> bool { + let nodes = &self.nodes; + let chain_state = &mut self.chain_state; + let payments = &mut self.payments; + // Multiple HTLCs can resolve for the same payment hash, so deduplicate + // claim/fail handling per event batch. + let mut claim_set = new_hash_map(); + let mut events = nodes[node_idx].get_and_clear_pending_events(); + let had_events = !events.is_empty(); + for event in events.drain(..) { + match event { + events::Event::PaymentClaimable { payment_hash, .. } => { + if claim_set.insert(payment_hash.0, ()).is_none() { + payments.claim_payment(&nodes[node_idx], payment_hash, fail); } - last_pass_no_updates = true; - } - }}; + }, + events::Event::PaymentSent { payment_id, payment_hash, .. } => { + payments.mark_sent(node_idx, payment_id.unwrap(), payment_hash); + }, + // Even though we don't explicitly send probes, because probes are detected based on + // hashing the payment hash+preimage, it is rather trivial for the fuzzer to build + // payments that accidentally end up looking like probes. + events::Event::ProbeSuccessful { payment_id, .. } => { + payments.mark_successful_probe(node_idx, payment_id); + }, + events::Event::PaymentFailed { payment_id, .. } + | events::Event::ProbeFailed { payment_id, .. } => { + payments.mark_resolved_without_hash(node_idx, payment_id); + }, + events::Event::PaymentClaimed { .. } => {}, + events::Event::PaymentPathSuccessful { .. } => {}, + events::Event::PaymentPathFailed { .. } => {}, + events::Event::PaymentForwarded { .. } if node_idx == 1 => {}, + events::Event::ChannelReady { .. } => {}, + events::Event::HTLCHandlingFailed { .. } => {}, + events::Event::FundingTransactionReadyForSigning { + channel_id, + counterparty_node_id, + unsigned_transaction, + .. + } => { + let signed_tx = nodes[node_idx].wallet.sign_tx(unsigned_transaction).unwrap(); + nodes[node_idx] + .funding_transaction_signed(&channel_id, &counterparty_node_id, signed_tx) + .unwrap(); + }, + events::Event::SplicePending { new_funding_txo, .. } => { + let mut txs = nodes[node_idx].broadcaster.txn_broadcasted.borrow_mut(); + assert!(txs.len() >= 1); + let splice_tx = txs.remove(0); + assert_eq!(new_funding_txo.txid, splice_tx.compute_txid()); + chain_state.add_pending_tx(splice_tx); + }, + events::Event::SpliceFailed { .. } => {}, + events::Event::DiscardFunding { + funding_info: + events::FundingInfo::Contribution { .. } | events::FundingInfo::Tx { .. }, + .. + } => {}, + _ => panic!("Unhandled event"), + } + } + while nodes[node_idx].needs_pending_htlc_processing() { + nodes[node_idx].process_pending_htlc_forwards(); + } + had_events + } + + fn process_msg_noret( + &mut self, node_idx: usize, corrupt_forward: bool, limit_events: ProcessMessages, + ) { + self.process_msg_events(node_idx, corrupt_forward, limit_events); + } + + fn process_ev_noret(&mut self, node_idx: usize, fail: bool) { + self.process_events(node_idx, fail); + } + + fn process_all_events(&mut self) { + let mut last_pass_no_updates = false; + for i in 0..std::usize::MAX { + if i == 100 { + panic!( + "It may take may iterations to settle the state, but it should not take forever" + ); + } + // Next, make sure no monitor updates are pending. + self.ab_link.complete_all_monitor_updates(&self.nodes); + self.bc_link.complete_all_monitor_updates(&self.nodes); + // Then, make sure any current forwards make their way to their destination. + if self.process_msg_events(0, false, ProcessMessages::AllMessages) { + last_pass_no_updates = false; + continue; + } + if self.process_msg_events(1, false, ProcessMessages::AllMessages) { + last_pass_no_updates = false; + continue; + } + if self.process_msg_events(2, false, ProcessMessages::AllMessages) { + last_pass_no_updates = false; + continue; + } + // ...making sure any payments are claimed. + if self.process_events(0, false) { + last_pass_no_updates = false; + continue; + } + if self.process_events(1, false) { + last_pass_no_updates = false; + continue; + } + if self.process_events(2, false) { + last_pass_no_updates = false; + continue; + } + if last_pass_no_updates { + // In some cases, we may generate a message to send in + // `process_msg_events`, but block sending until + // `complete_all_monitor_updates` gets called on the next + // iteration. + // + // Thus, we only exit if we manage two iterations with no messages + // or events to process. + break; + } + last_pass_no_updates = true; + } + } + + fn disconnect_ab(&mut self) { + self.ab_link.disconnect(&self.nodes, &mut self.queues); + } + + fn disconnect_bc(&mut self) { + self.bc_link.disconnect(&self.nodes, &mut self.queues); + } + + fn reconnect_ab(&mut self) { + self.ab_link.reconnect(&self.nodes); + } + + fn reconnect_bc(&mut self) { + self.bc_link.reconnect(&self.nodes); + } + + fn restart_node(&mut self, node_idx: usize, v: u8, router: &'a FuzzRouter) { + match node_idx { + 0 => { + self.ab_link.disconnect_for_reload(0, &self.nodes, &mut self.queues); + }, + 1 => { + self.ab_link.disconnect_for_reload(1, &self.nodes, &mut self.queues); + self.bc_link.disconnect_for_reload(1, &self.nodes, &mut self.queues); + }, + 2 => { + self.bc_link.disconnect_for_reload(2, &self.nodes, &mut self.queues); + }, + _ => panic!("invalid node index"), + } + self.nodes[node_idx].reload(v, &self.out, router, self.chan_type); + } + + fn settle_all(&mut self) { + // First, make sure peers are all connected to each other + self.reconnect_ab(); + self.reconnect_bc(); + + for op in SUPPORTED_SIGNER_OPS { + self.nodes[0].keys_manager.enable_op_for_all_signers(op); + self.nodes[1].keys_manager.enable_op_for_all_signers(op); + self.nodes[2].keys_manager.enable_op_for_all_signers(op); + } + self.nodes[0].signer_unblocked(None); + self.nodes[1].signer_unblocked(None); + self.nodes[2].signer_unblocked(None); + + self.process_all_events(); + + // Since MPP payments are supported, we wait until we fully settle the state of all + // channels to see if we have any committed HTLC parts of an MPP payment that need + // to be failed back. + for node in self.nodes.iter() { + node.timer_tick_occurred(); + } + self.process_all_events(); + + // Verify no payments are stuck - all should have resolved + self.payments.assert_all_resolved(); + // Verify that every payment claimed by a receiver resulted in a + // PaymentSent event at the sender. + self.payments.assert_claims_reported(); + + // Finally, make sure that at least one end of each channel can make a substantial payment. + let chan_ab_ids = self.ab_link.channel_ids().clone(); + let chan_bc_ids = self.bc_link.channel_ids().clone(); + for chan_id in chan_ab_ids { + assert!( + self.send_on_channel(0, 1, chan_id, 10_000_000) + || self.send_on_channel(1, 0, chan_id, 10_000_000) + ); + } + for chan_id in chan_bc_ids { + assert!( + self.send_on_channel(1, 2, chan_id, 10_000_000) + || self.send_on_channel(2, 1, chan_id, 10_000_000) + ); } + self.nodes[0].record_last_htlc_clear_fee(); + self.nodes[1].record_last_htlc_clear_fee(); + self.nodes[2].record_last_htlc_clear_fee(); + } + + fn refresh_serialized_managers(&mut self) { + for node in &mut self.nodes { + node.refresh_serialized_manager(); + } + } +} + +#[inline] +pub fn do_test(data: &[u8], out: Out) { + let router = FuzzRouter {}; + // Read initial monitor styles and channel type from fuzz input byte 0: + // bits 0-2: monitor styles (1 bit per node) + // bits 3-4: channel type (0=Legacy, 1=KeyedAnchors, 2=ZeroFeeCommitments) + let config_byte = if !data.is_empty() { data[0] } else { 0 }; + let mut harness = Harness::new(config_byte, out, &router); + let mut read_pos = 1; // First byte was consumed for initial config. + + 'fuzz_loop: loop { if data.len() < read_pos + 1 { break 'fuzz_loop; } let v = data[read_pos]; read_pos += 1; - out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); + harness + .out + .locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); match v { // In general, we keep related message groups close together in binary form, allowing // bit-twiddling mutations to have similar effects. This is probably overkill, but no // harm in doing so. - 0x00 => nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), - 0x01 => nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), - 0x02 => nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), - 0x04 => nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::Completed), - 0x05 => nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::Completed), - 0x06 => nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::Completed), + 0x00 => harness.nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x01 => harness.nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x02 => harness.nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::InProgress), + 0x04 => harness.nodes[0].set_persistence_style(ChannelMonitorUpdateStatus::Completed), + 0x05 => harness.nodes[1].set_persistence_style(ChannelMonitorUpdateStatus::Completed), + 0x06 => harness.nodes[2].set_persistence_style(ChannelMonitorUpdateStatus::Completed), 0x08 => { - for id in ab_link.channel_ids() { - nodes[0].complete_all_monitor_updates(id); + for id in harness.ab_link.channel_ids() { + harness.nodes[0].complete_all_monitor_updates(id); } }, 0x09 => { - for id in ab_link.channel_ids() { - nodes[1].complete_all_monitor_updates(id); + for id in harness.ab_link.channel_ids() { + harness.nodes[1].complete_all_monitor_updates(id); } }, 0x0a => { - for id in bc_link.channel_ids() { - nodes[1].complete_all_monitor_updates(id); + for id in harness.bc_link.channel_ids() { + harness.nodes[1].complete_all_monitor_updates(id); } }, 0x0b => { - for id in bc_link.channel_ids() { - nodes[2].complete_all_monitor_updates(id); + for id in harness.bc_link.channel_ids() { + harness.nodes[2].complete_all_monitor_updates(id); } }, - 0x0c => ab_link.disconnect(&nodes, &mut queues), - 0x0d => bc_link.disconnect(&nodes, &mut queues), - 0x0e => ab_link.reconnect(&nodes), - 0x0f => bc_link.reconnect(&nodes), + 0x0c => harness.disconnect_ab(), + 0x0d => harness.disconnect_bc(), + 0x0e => harness.reconnect_ab(), + 0x0f => harness.reconnect_bc(), - 0x10 => process_msg_noret!(0, true, ProcessMessages::AllMessages), - 0x11 => process_msg_noret!(0, false, ProcessMessages::AllMessages), - 0x12 => process_msg_noret!(0, true, ProcessMessages::OneMessage), - 0x13 => process_msg_noret!(0, false, ProcessMessages::OneMessage), - 0x14 => process_msg_noret!(0, true, ProcessMessages::OnePendingMessage), - 0x15 => process_msg_noret!(0, false, ProcessMessages::OnePendingMessage), + 0x10 => harness.process_msg_noret(0, true, ProcessMessages::AllMessages), + 0x11 => harness.process_msg_noret(0, false, ProcessMessages::AllMessages), + 0x12 => harness.process_msg_noret(0, true, ProcessMessages::OneMessage), + 0x13 => harness.process_msg_noret(0, false, ProcessMessages::OneMessage), + 0x14 => harness.process_msg_noret(0, true, ProcessMessages::OnePendingMessage), + 0x15 => harness.process_msg_noret(0, false, ProcessMessages::OnePendingMessage), - 0x16 => process_ev_noret!(0, true), - 0x17 => process_ev_noret!(0, false), + 0x16 => harness.process_ev_noret(0, true), + 0x17 => harness.process_ev_noret(0, false), - 0x18 => process_msg_noret!(1, true, ProcessMessages::AllMessages), - 0x19 => process_msg_noret!(1, false, ProcessMessages::AllMessages), - 0x1a => process_msg_noret!(1, true, ProcessMessages::OneMessage), - 0x1b => process_msg_noret!(1, false, ProcessMessages::OneMessage), - 0x1c => process_msg_noret!(1, true, ProcessMessages::OnePendingMessage), - 0x1d => process_msg_noret!(1, false, ProcessMessages::OnePendingMessage), + 0x18 => harness.process_msg_noret(1, true, ProcessMessages::AllMessages), + 0x19 => harness.process_msg_noret(1, false, ProcessMessages::AllMessages), + 0x1a => harness.process_msg_noret(1, true, ProcessMessages::OneMessage), + 0x1b => harness.process_msg_noret(1, false, ProcessMessages::OneMessage), + 0x1c => harness.process_msg_noret(1, true, ProcessMessages::OnePendingMessage), + 0x1d => harness.process_msg_noret(1, false, ProcessMessages::OnePendingMessage), - 0x1e => process_ev_noret!(1, true), - 0x1f => process_ev_noret!(1, false), + 0x1e => harness.process_ev_noret(1, true), + 0x1f => harness.process_ev_noret(1, false), - 0x20 => process_msg_noret!(2, true, ProcessMessages::AllMessages), - 0x21 => process_msg_noret!(2, false, ProcessMessages::AllMessages), - 0x22 => process_msg_noret!(2, true, ProcessMessages::OneMessage), - 0x23 => process_msg_noret!(2, false, ProcessMessages::OneMessage), - 0x24 => process_msg_noret!(2, true, ProcessMessages::OnePendingMessage), - 0x25 => process_msg_noret!(2, false, ProcessMessages::OnePendingMessage), + 0x20 => harness.process_msg_noret(2, true, ProcessMessages::AllMessages), + 0x21 => harness.process_msg_noret(2, false, ProcessMessages::AllMessages), + 0x22 => harness.process_msg_noret(2, true, ProcessMessages::OneMessage), + 0x23 => harness.process_msg_noret(2, false, ProcessMessages::OneMessage), + 0x24 => harness.process_msg_noret(2, true, ProcessMessages::OnePendingMessage), + 0x25 => harness.process_msg_noret(2, false, ProcessMessages::OnePendingMessage), - 0x26 => process_ev_noret!(2, true), - 0x27 => process_ev_noret!(2, false), + 0x26 => harness.process_ev_noret(2, true), + 0x27 => harness.process_ev_noret(2, false), // 1/10th the channel size: - 0x30 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10_000_000), - 0x31 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10_000_000), - 0x32 => payments.send_noret(&nodes, 1, 2, chan_b_id, 10_000_000), - 0x33 => payments.send_noret(&nodes, 2, 1, chan_b_id, 10_000_000), - 0x34 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10_000_000), - 0x35 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10_000_000), - - 0x38 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1_000_000), - 0x39 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1_000_000), - 0x3a => payments.send_noret(&nodes, 1, 2, chan_b_id, 1_000_000), - 0x3b => payments.send_noret(&nodes, 2, 1, chan_b_id, 1_000_000), - 0x3c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1_000_000), - 0x3d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1_000_000), - - 0x40 => payments.send_noret(&nodes, 0, 1, chan_a_id, 100_000), - 0x41 => payments.send_noret(&nodes, 1, 0, chan_a_id, 100_000), - 0x42 => payments.send_noret(&nodes, 1, 2, chan_b_id, 100_000), - 0x43 => payments.send_noret(&nodes, 2, 1, chan_b_id, 100_000), - 0x44 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 100_000), - 0x45 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 100_000), - - 0x48 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10_000), - 0x49 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10_000), - 0x4a => payments.send_noret(&nodes, 1, 2, chan_b_id, 10_000), - 0x4b => payments.send_noret(&nodes, 2, 1, chan_b_id, 10_000), - 0x4c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10_000), - 0x4d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10_000), - - 0x50 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1_000), - 0x51 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1_000), - 0x52 => payments.send_noret(&nodes, 1, 2, chan_b_id, 1_000), - 0x53 => payments.send_noret(&nodes, 2, 1, chan_b_id, 1_000), - 0x54 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1_000), - 0x55 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1_000), - - 0x58 => payments.send_noret(&nodes, 0, 1, chan_a_id, 100), - 0x59 => payments.send_noret(&nodes, 1, 0, chan_a_id, 100), - 0x5a => payments.send_noret(&nodes, 1, 2, chan_b_id, 100), - 0x5b => payments.send_noret(&nodes, 2, 1, chan_b_id, 100), - 0x5c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 100), - 0x5d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 100), - - 0x60 => payments.send_noret(&nodes, 0, 1, chan_a_id, 10), - 0x61 => payments.send_noret(&nodes, 1, 0, chan_a_id, 10), - 0x62 => payments.send_noret(&nodes, 1, 2, chan_b_id, 10), - 0x63 => payments.send_noret(&nodes, 2, 1, chan_b_id, 10), - 0x64 => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 10), - 0x65 => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 10), - - 0x68 => payments.send_noret(&nodes, 0, 1, chan_a_id, 1), - 0x69 => payments.send_noret(&nodes, 1, 0, chan_a_id, 1), - 0x6a => payments.send_noret(&nodes, 1, 2, chan_b_id, 1), - 0x6b => payments.send_noret(&nodes, 2, 1, chan_b_id, 1), - 0x6c => payments.send_hop_noret(&nodes, 0, 1, chan_a_id, 2, chan_b_id, 1), - 0x6d => payments.send_hop_noret(&nodes, 2, 1, chan_b_id, 0, chan_a_id, 1), + 0x30 => harness.send(0, 1, 10_000_000), + 0x31 => harness.send(1, 0, 10_000_000), + 0x32 => harness.send(1, 2, 10_000_000), + 0x33 => harness.send(2, 1, 10_000_000), + 0x34 => harness.send_hop(0, 1, 2, 10_000_000), + 0x35 => harness.send_hop(2, 1, 0, 10_000_000), + + 0x38 => harness.send(0, 1, 1_000_000), + 0x39 => harness.send(1, 0, 1_000_000), + 0x3a => harness.send(1, 2, 1_000_000), + 0x3b => harness.send(2, 1, 1_000_000), + 0x3c => harness.send_hop(0, 1, 2, 1_000_000), + 0x3d => harness.send_hop(2, 1, 0, 1_000_000), + + 0x40 => harness.send(0, 1, 100_000), + 0x41 => harness.send(1, 0, 100_000), + 0x42 => harness.send(1, 2, 100_000), + 0x43 => harness.send(2, 1, 100_000), + 0x44 => harness.send_hop(0, 1, 2, 100_000), + 0x45 => harness.send_hop(2, 1, 0, 100_000), + + 0x48 => harness.send(0, 1, 10_000), + 0x49 => harness.send(1, 0, 10_000), + 0x4a => harness.send(1, 2, 10_000), + 0x4b => harness.send(2, 1, 10_000), + 0x4c => harness.send_hop(0, 1, 2, 10_000), + 0x4d => harness.send_hop(2, 1, 0, 10_000), + + 0x50 => harness.send(0, 1, 1_000), + 0x51 => harness.send(1, 0, 1_000), + 0x52 => harness.send(1, 2, 1_000), + 0x53 => harness.send(2, 1, 1_000), + 0x54 => harness.send_hop(0, 1, 2, 1_000), + 0x55 => harness.send_hop(2, 1, 0, 1_000), + + 0x58 => harness.send(0, 1, 100), + 0x59 => harness.send(1, 0, 100), + 0x5a => harness.send(1, 2, 100), + 0x5b => harness.send(2, 1, 100), + 0x5c => harness.send_hop(0, 1, 2, 100), + 0x5d => harness.send_hop(2, 1, 0, 100), + + 0x60 => harness.send(0, 1, 10), + 0x61 => harness.send(1, 0, 10), + 0x62 => harness.send(1, 2, 10), + 0x63 => harness.send(2, 1, 10), + 0x64 => harness.send_hop(0, 1, 2, 10), + 0x65 => harness.send_hop(2, 1, 0, 10), + + 0x68 => harness.send(0, 1, 1), + 0x69 => harness.send(1, 0, 1), + 0x6a => harness.send(1, 2, 1), + 0x6b => harness.send(2, 1, 1), + 0x6c => harness.send_hop(0, 1, 2, 1), + 0x6d => harness.send_hop(2, 1, 0, 1), // MPP payments // 0x70: direct MPP from 0 to 1 (multi A-B channels) - 0x70 => payments.send_mpp_direct(&nodes, 0, 1, ab_link.channel_ids(), 1_000_000), + 0x70 => harness.send_mpp_direct(0, 1, MppDirectChannels::All, 1_000_000), // 0x71: MPP 0->1->2, multi channels on first hop (A-B) - 0x71 => payments.send_mpp_hop_noret( - &nodes, - 0, - 1, - ab_link.channel_ids(), - 2, - &[chan_b_id], - 1_000_000, - ), + 0x71 => harness.send_mpp_hop(0, 1, 2, MppHopChannels::FirstHop, 1_000_000), // 0x72: MPP 0->1->2, multi channels on both hops (A-B and B-C) - 0x72 => payments.send_mpp_hop_noret( - &nodes, - 0, - 1, - ab_link.channel_ids(), - 2, - bc_link.channel_ids(), - 1_000_000, - ), + 0x72 => harness.send_mpp_hop(0, 1, 2, MppHopChannels::BothHops, 1_000_000), // 0x73: MPP 0->1->2, multi channels on second hop (B-C) - 0x73 => payments.send_mpp_hop_noret( - &nodes, - 0, - 1, - &[chan_a_id], - 2, - bc_link.channel_ids(), - 1_000_000, - ), + 0x73 => harness.send_mpp_hop(0, 1, 2, MppHopChannels::SecondHop, 1_000_000), // 0x74: direct MPP from 0 to 1, multi parts over single channel - 0x74 => { - payments.send_mpp_direct( - &nodes, - 0, - 1, - &[chan_a_id, chan_a_id, chan_a_id], - 1_000_000, - ); - }, + 0x74 => harness.send_mpp_direct(0, 1, MppDirectChannels::RepeatedFirst, 1_000_000), - 0x80 => nodes[0].bump_fee_estimate(chan_type), - 0x81 => nodes[0].reset_fee_estimate(), - 0x84 => nodes[1].bump_fee_estimate(chan_type), - 0x85 => nodes[1].reset_fee_estimate(), - 0x88 => nodes[2].bump_fee_estimate(chan_type), - 0x89 => nodes[2].reset_fee_estimate(), + 0x80 => harness.nodes[0].bump_fee_estimate(harness.chan_type), + 0x81 => harness.nodes[0].reset_fee_estimate(), + 0x84 => harness.nodes[1].bump_fee_estimate(harness.chan_type), + 0x85 => harness.nodes[1].reset_fee_estimate(), + 0x88 => harness.nodes[2].bump_fee_estimate(harness.chan_type), + 0x89 => harness.nodes[2].reset_fee_estimate(), 0xa0 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[1].get_our_node_id(); - nodes[0].splice_in(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[1].get_our_node_id(); + harness.nodes[0].splice_in(&cp_node_id, &harness.chan_a_id()); }, 0xa1 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[0].get_our_node_id(); - nodes[1].splice_in(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[0].get_our_node_id(); + harness.nodes[1].splice_in(&cp_node_id, &harness.chan_a_id()); }, 0xa2 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[2].get_our_node_id(); - nodes[1].splice_in(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[2].get_our_node_id(); + harness.nodes[1].splice_in(&cp_node_id, &harness.chan_b_id()); }, 0xa3 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[1].get_our_node_id(); - nodes[2].splice_in(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[1].get_our_node_id(); + harness.nodes[2].splice_in(&cp_node_id, &harness.chan_b_id()); }, 0xa4 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[1].get_our_node_id(); - nodes[0].splice_out(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[1].get_our_node_id(); + harness.nodes[0].splice_out(&cp_node_id, &harness.chan_a_id()); }, 0xa5 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[0].get_our_node_id(); - nodes[1].splice_out(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[0].get_our_node_id(); + harness.nodes[1].splice_out(&cp_node_id, &harness.chan_a_id()); }, 0xa6 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[2].get_our_node_id(); - nodes[1].splice_out(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[2].get_our_node_id(); + harness.nodes[1].splice_out(&cp_node_id, &harness.chan_b_id()); }, 0xa7 => { if !cfg!(splicing) { break 'fuzz_loop; } - let cp_node_id = nodes[1].get_our_node_id(); - nodes[2].splice_out(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[1].get_our_node_id(); + harness.nodes[2].splice_out(&cp_node_id, &harness.chan_b_id()); }, // Sync node by 1 block to cover confirmation of a transaction. 0xa8 => { - chain_state.confirm_pending_txs(); - nodes[0].sync_with_chain_state(&chain_state, Some(1)); + harness.chain_state.confirm_pending_txs(); + harness.nodes[0].sync_with_chain_state(&harness.chain_state, Some(1)); }, 0xa9 => { - chain_state.confirm_pending_txs(); - nodes[1].sync_with_chain_state(&chain_state, Some(1)); + harness.chain_state.confirm_pending_txs(); + harness.nodes[1].sync_with_chain_state(&harness.chain_state, Some(1)); }, 0xaa => { - chain_state.confirm_pending_txs(); - nodes[2].sync_with_chain_state(&chain_state, Some(1)); + harness.chain_state.confirm_pending_txs(); + harness.nodes[2].sync_with_chain_state(&harness.chain_state, Some(1)); }, // Sync node to chain tip to cover confirmation of a transaction post-reorg-risk. 0xab => { - chain_state.confirm_pending_txs(); - nodes[0].sync_with_chain_state(&chain_state, None); + harness.chain_state.confirm_pending_txs(); + harness.nodes[0].sync_with_chain_state(&harness.chain_state, None); }, 0xac => { - chain_state.confirm_pending_txs(); - nodes[1].sync_with_chain_state(&chain_state, None); + harness.chain_state.confirm_pending_txs(); + harness.nodes[1].sync_with_chain_state(&harness.chain_state, None); }, 0xad => { - chain_state.confirm_pending_txs(); - nodes[2].sync_with_chain_state(&chain_state, None); + harness.chain_state.confirm_pending_txs(); + harness.nodes[2].sync_with_chain_state(&harness.chain_state, None); }, 0xb0 | 0xb1 | 0xb2 => { // Restart node A, picking among the in-flight `ChannelMonitor`s to use based on // the value of `v` we're matching. - ab_link.disconnect_for_reload(0, &nodes, &mut queues); - nodes[0].reload(v, &out, &router, chan_type); + harness.restart_node(0, v, &router); }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on // the value of `v` we're matching. - ab_link.disconnect_for_reload(1, &nodes, &mut queues); - bc_link.disconnect_for_reload(1, &nodes, &mut queues); - nodes[1].reload(v, &out, &router, chan_type); + harness.restart_node(1, v, &router); }, 0xbc | 0xbd | 0xbe => { // Restart node C, picking among the in-flight `ChannelMonitor`s to use based on // the value of `v` we're matching. - bc_link.disconnect_for_reload(2, &nodes, &mut queues); - nodes[2].reload(v, &out, &router, chan_type); + harness.restart_node(2, v, &router); }, - 0xc0 => nodes[0].keys_manager.disable_supported_ops_for_all_signers(), - 0xc1 => nodes[1].keys_manager.disable_supported_ops_for_all_signers(), - 0xc2 => nodes[2].keys_manager.disable_supported_ops_for_all_signers(), + 0xc0 => harness.nodes[0].keys_manager.disable_supported_ops_for_all_signers(), + 0xc1 => harness.nodes[1].keys_manager.disable_supported_ops_for_all_signers(), + 0xc2 => harness.nodes[2].keys_manager.disable_supported_ops_for_all_signers(), 0xc3 => { - nodes[0] + harness.nodes[0] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - nodes[0].signer_unblocked(None); + harness.nodes[0].signer_unblocked(None); }, 0xc4 => { - nodes[1] + harness.nodes[1] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); + let filter = Some((harness.nodes[0].get_our_node_id(), harness.chan_a_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xc5 => { - nodes[1] + harness.nodes[1] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); + let filter = Some((harness.nodes[2].get_our_node_id(), harness.chan_b_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xc6 => { - nodes[2] + harness.nodes[2] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - nodes[2].signer_unblocked(None); + harness.nodes[2].signer_unblocked(None); }, 0xc7 => { - nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[0].signer_unblocked(None); + harness.nodes[0] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + harness.nodes[0].signer_unblocked(None); }, 0xc8 => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + let filter = Some((harness.nodes[0].get_our_node_id(), harness.chan_a_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xc9 => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + let filter = Some((harness.nodes[2].get_our_node_id(), harness.chan_b_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xca => { - nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[2].signer_unblocked(None); + harness.nodes[2] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + harness.nodes[2].signer_unblocked(None); }, 0xcb => { - nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[0].signer_unblocked(None); + harness.nodes[0] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + harness.nodes[0].signer_unblocked(None); }, 0xcc => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + let filter = Some((harness.nodes[0].get_our_node_id(), harness.chan_a_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xcd => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + let filter = Some((harness.nodes[2].get_our_node_id(), harness.chan_b_id())); + harness.nodes[1].signer_unblocked(filter); }, 0xce => { - nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[2].signer_unblocked(None); + harness.nodes[2] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + harness.nodes[2].signer_unblocked(None); }, - 0xf0 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::First) - }, - 0xf1 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Second) - }, - 0xf2 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Last) - }, + 0xf0 => harness.ab_link.complete_monitor_updates_for_node( + 0, + &harness.nodes, + MonitorUpdateSelector::First, + ), + 0xf1 => harness.ab_link.complete_monitor_updates_for_node( + 0, + &harness.nodes, + MonitorUpdateSelector::Second, + ), + 0xf2 => harness.ab_link.complete_monitor_updates_for_node( + 0, + &harness.nodes, + MonitorUpdateSelector::Last, + ), - 0xf4 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) - }, - 0xf5 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) - }, - 0xf6 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) - }, + 0xf4 => harness.ab_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::First, + ), + 0xf5 => harness.ab_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Second, + ), + 0xf6 => harness.ab_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Last, + ), - 0xf8 => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) - }, - 0xf9 => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) - }, - 0xfa => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) - }, + 0xf8 => harness.bc_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::First, + ), + 0xf9 => harness.bc_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Second, + ), + 0xfa => harness.bc_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Last, + ), - 0xfc => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::First) - }, - 0xfd => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Second) - }, - 0xfe => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Last) - }, + 0xfc => harness.bc_link.complete_monitor_updates_for_node( + 2, + &harness.nodes, + MonitorUpdateSelector::First, + ), + 0xfd => harness.bc_link.complete_monitor_updates_for_node( + 2, + &harness.nodes, + MonitorUpdateSelector::Second, + ), + 0xfe => harness.bc_link.complete_monitor_updates_for_node( + 2, + &harness.nodes, + MonitorUpdateSelector::Last, + ), 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 - ab_link.reconnect(&nodes); - bc_link.reconnect(&nodes); - - for op in SUPPORTED_SIGNER_OPS { - nodes[0].keys_manager.enable_op_for_all_signers(op); - nodes[1].keys_manager.enable_op_for_all_signers(op); - nodes[2].keys_manager.enable_op_for_all_signers(op); - } - nodes[0].signer_unblocked(None); - nodes[1].signer_unblocked(None); - nodes[2].signer_unblocked(None); - - process_all_events!(); - - // Since MPP payments are supported, we wait until we fully settle the state of all - // channels to see if we have any committed HTLC parts of an MPP payment that need - // to be failed back. - for node in &nodes { - node.timer_tick_occurred(); - } - process_all_events!(); - - // Verify no payments are stuck - all should have resolved - payments.assert_all_resolved(); - // Verify that every payment claimed by a receiver resulted in a - // PaymentSent event at the sender. - payments.assert_claims_reported(); - - // Finally, make sure that at least one end of each channel can make a substantial payment - for &chan_id in ab_link.channel_ids() { - assert!( - payments.send(&nodes, 0, 1, chan_id, 10_000_000) - || payments.send(&nodes, 1, 0, chan_id, 10_000_000) - ); - } - for &chan_id in bc_link.channel_ids() { - assert!( - payments.send(&nodes, 1, 2, chan_id, 10_000_000) - || payments.send(&nodes, 2, 1, chan_id, 10_000_000) - ); - } - - nodes[0].record_last_htlc_clear_fee(); - nodes[1].record_last_htlc_clear_fee(); - nodes[2].record_last_htlc_clear_fee(); + harness.settle_all(); }, _ => break 'fuzz_loop, } - for node in &mut nodes { - node.refresh_serialized_manager(); - } + harness.refresh_serialized_managers(); } - assert_test_invariants(&nodes); + harness.finish(); } pub fn chanmon_consistency_test(data: &[u8], out: Out) {