From 809bc76aacc7cf5e1258ea6279e366c4b6bef3ca Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:45:06 +0200 Subject: [PATCH 01/19] Extract chanmon bootstrap helpers --- fuzz/src/chanmon_consistency.rs | 563 +++++++++++++++++--------------- 1 file changed, 297 insertions(+), 266 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 73b3f22d4d6..b0f96df2dde 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -869,12 +869,238 @@ 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_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_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()) }); @@ -938,24 +1164,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 = ChainParameters { network, best_block: BestBlock::from_network(network) }; @@ -970,7 +1178,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, ), @@ -1001,25 +1209,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() { @@ -1069,7 +1258,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, }; @@ -1086,224 +1275,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()); @@ -1345,8 +1316,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). // @@ -1354,14 +1325,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`. @@ -1395,7 +1426,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 ba32e193c149c4eb86f37ecf3ff5d27027c6bd29 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:31:38 +0200 Subject: [PATCH 02/19] Wrap chanmon nodes in HarnessNode --- fuzz/src/chanmon_consistency.rs | 249 ++++++++++++++------------------ 1 file changed, 110 insertions(+), 139 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index b0f96df2dde..6b915a5a778 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -876,6 +876,38 @@ 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 our_node_id(&self) -> PublicKey { + self.node.get_our_node_id() + } + + 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; @@ -898,18 +930,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 }; @@ -920,26 +940,19 @@ 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 - .create_channel_to_trusted_peer_0reserve( - dest.get_our_node_id(), - 100_000, - 42, - 0, - None, - None, - ) + .node + .create_channel_to_trusted_peer_0reserve(dest.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(); + source.node.create_channel(dest.our_node_id(), 100_000, 42, 0, None, None).unwrap(); } let open_channel = { - let events = source.get_and_clear_pending_msg_events(); + let events = source.node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); if let MessageSendEvent::SendOpenChannel { ref msg, .. } = events[0] { msg.clone() @@ -948,9 +961,9 @@ fn make_channel( } }; - dest.handle_open_channel(source.get_our_node_id(), &open_channel); + dest.node.handle_open_channel(source.our_node_id(), &open_channel); let accept_channel = { - let events = dest.get_and_clear_pending_events(); + let events = dest.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); if let events::Event::OpenChannelRequest { ref temporary_channel_id, @@ -959,30 +972,32 @@ 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( - temporary_channel_id, - counterparty_node_id, - user_channel_id, - TrustedChannelFeatures::ZeroReserve, - None, - ) - .unwrap(); + dest.node + .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(); + dest.node + .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(); + let events = dest.node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); if let MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] { msg.clone() @@ -991,9 +1006,9 @@ fn make_channel( } }; - source.handle_accept_channel(dest.get_our_node_id(), &accept_channel); + source.node.handle_accept_channel(dest.our_node_id(), &accept_channel); { - let mut events = source.get_and_clear_pending_events(); + let mut events = source.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); if let events::Event::FundingGenerationReady { temporary_channel_id, @@ -1012,11 +1027,8 @@ fn make_channel( }], }; source - .funding_transaction_generated( - temporary_channel_id, - dest.get_our_node_id(), - tx.clone(), - ) + .node + .funding_transaction_generated(temporary_channel_id, dest.our_node_id(), tx.clone()) .unwrap(); chain_state.confirm_tx(tx); } else { @@ -1025,7 +1037,7 @@ fn make_channel( } let funding_created = { - let events = source.get_and_clear_pending_msg_events(); + let events = source.node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); if let MessageSendEvent::SendFundingCreated { ref msg, .. } = events[0] { msg.clone() @@ -1033,11 +1045,11 @@ fn make_channel( panic!("Wrong event type"); } }; - dest.handle_funding_created(source.get_our_node_id(), &funding_created); - complete_all_pending_monitor_updates(dest_monitor); + dest.node.handle_funding_created(source.our_node_id(), &funding_created); + dest.complete_all_pending_monitor_updates(); let (funding_signed, channel_id) = { - let events = dest.get_and_clear_pending_msg_events(); + let events = dest.node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); if let MessageSendEvent::SendFundingSigned { ref msg, .. } = events[0] { (msg.clone(), msg.channel_id) @@ -1045,18 +1057,18 @@ fn make_channel( panic!("Wrong event type"); } }; - let events = dest.get_and_clear_pending_events(); + let events = dest.node.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()); + assert_eq!(counterparty_node_id, &source.our_node_id()); } else { panic!("Wrong event type"); } - source.handle_funding_signed(dest.get_our_node_id(), &funding_signed); - complete_all_pending_monitor_updates(source_monitor); + source.node.handle_funding_signed(dest.our_node_id(), &funding_signed); + source.complete_all_pending_monitor_updates(); - let events = source.get_and_clear_pending_events(); + let events = source.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); if let events::Event::ChannelPending { ref counterparty_node_id, @@ -1064,24 +1076,24 @@ fn make_channel( .. } = events[0] { - assert_eq!(counterparty_node_id, &dest.get_our_node_id()); + assert_eq!(counterparty_node_id, &dest.our_node_id()); assert_eq!(*event_channel_id, channel_id); } else { panic!("Wrong event type"); } } -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()); + node_events.push(node.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); + if node.our_node_id() == *node_id { + node.node.handle_channel_ready(nodes[idx].our_node_id(), msg); } } } else { @@ -1091,7 +1103,7 @@ fn lock_fundings(nodes: &[ChanMan<'_>; 3]) { } for node in nodes.iter() { - let events = node.get_and_clear_pending_msg_events(); + let events = node.node.get_and_clear_pending_msg_events(); for event in events { if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { } else { @@ -1309,7 +1321,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)] @@ -1325,74 +1353,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`. @@ -2576,8 +2544,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 @@ -2605,8 +2574,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 @@ -2630,8 +2600,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 2fe3ec3f382b436172a91a7bb6c26f2aa5f8d2c2 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 18:00:00 +0200 Subject: [PATCH 03/19] Build chanmon node resources --- fuzz/src/chanmon_consistency.rs | 682 ++++++++++++++++---------------- 1 file changed, 349 insertions(+), 333 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 6b915a5a778..b10db689ffb 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -880,6 +880,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> { @@ -891,6 +895,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: BestBlock::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 our_node_id(&self) -> PublicKey { self.node.get_our_node_id() } @@ -930,6 +1000,16 @@ 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); + + 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 }; @@ -1115,9 +1195,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: @@ -1151,162 +1228,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: BestBlock::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 = <(BestBlock, 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 = - <(BestBlock, 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; @@ -1314,34 +1255,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]); @@ -1364,12 +1321,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 { @@ -1377,7 +1334,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); @@ -1390,9 +1346,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); @@ -1432,20 +1388,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 = <(BestBlock, 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 = + <(BestBlock, 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) => {{ @@ -1458,88 +1487,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, - logger: Arc, - 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_sync( - outputs, - feerate, - FeeRate::MAX, - &WalletSync::new(wallet, logger.clone()), - ) - }); - }; - loop { // Push any events from Node B onto ba_events and bc_events macro_rules! push_excess_b_events { @@ -2019,7 +1966,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, @@ -2029,12 +1977,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()); @@ -2082,7 +2025,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!( @@ -2099,6 +2041,90 @@ 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_sync( + outputs, + feerate, + FeeRate::MAX, + &WalletSync::new(&node.wallet, Arc::clone(&node.logger)), + ) + }, + ); + }; + let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { let source = &nodes[source_idx]; @@ -2395,43 +2421,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(); }, @@ -2440,36 +2470,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 => { @@ -2477,50 +2499,38 @@ 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 logger = Arc::clone(&loggers[0]); - let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw(); - splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, logger, 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 logger = Arc::clone(&loggers[1]); - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, logger, 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 logger = Arc::clone(&loggers[1]); - let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw(); - splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, logger, 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 logger = Arc::clone(&loggers[2]); - let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw(); - splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, logger, 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 => sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, Some(1)), - 0xa9 => sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, Some(1)), - 0xaa => sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, Some(1)), + 0xa8 => sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, Some(1)), + 0xa9 => sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, Some(1)), + 0xaa => 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 => sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, None), - 0xac => sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None), - 0xad => sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None), + 0xab => sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None), + 0xac => sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None), + 0xad => sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None), 0xb0 | 0xb1 | 0xb2 => { // Restart node A, picking among the in-flight `ChannelMonitor`s to use based on @@ -2535,18 +2545,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 @@ -2565,18 +2576,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 @@ -2591,18 +2603,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(), @@ -2877,20 +2890,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 a7fa6cd6cb9416263e871a218c7a7c366a749a2f Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 18:00:24 +0200 Subject: [PATCH 04/19] Extract chanmon harness nodes --- 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 b10db689ffb..eba8585a504 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1293,12 +1293,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]); @@ -1393,25 +1387,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 @@ -1431,7 +1418,7 @@ pub fn do_test(data: &[u8], out: Out) { use_old_mons /= 3; let mon = <(BestBlock, 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); @@ -1450,12 +1437,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), @@ -2245,22 +2232,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); } }, @@ -2545,17 +2532,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; }, @@ -2576,17 +2555,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; }, @@ -2603,140 +2574,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); } }, @@ -2777,9 +2748,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); @@ -2794,12 +2765,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 752977fd0db8ebea987e42a99b4668dd7a1bbe40 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:24:01 +0200 Subject: [PATCH 05/19] Extract chanmon harness node lifecycle --- fuzz/src/chanmon_consistency.rs | 328 +++++++++++++++----------------- 1 file changed, 158 insertions(+), 170 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index eba8585a504..670262c8642 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -877,6 +877,7 @@ enum ChanType { } struct HarnessNode<'a> { + node_id: u8, node: ChanMan<'a>, monitor: Arc, keys_manager: Arc, @@ -884,6 +885,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> { @@ -958,13 +963,31 @@ 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 our_node_id(&self) -> PublicKey { self.node.get_our_node_id() } + fn set_persistence_style(&mut self, style: ChannelMonitorUpdateStatus) { + self.persistence_style = style; + *self.monitor.persister.update_ret.lock().unwrap() = style; + } + 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(..) { @@ -976,6 +999,84 @@ 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 { + (prev_state.persisted_monitor_id, prev_state.persisted_monitor) + } else if use_old_mons % 3 == 1 { + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.drain(..).next().unwrap_or(old_mon) + } else { + let old_mon = (prev_state.persisted_monitor_id, prev_state.persisted_monitor); + prev_state.pending_monitors.pop().unwrap_or(old_mon) + }; + use_old_mons /= 3; + let mon = <(BestBlock, ChannelMonitor)>::read( + &mut &serialized_mon[..], + (&*self.keys_manager, &*self.keys_manager), + ) + .expect("Failed to read monitor"); + monitors.insert(channel_id, mon.1); + prev_state.persisted_monitor = serialized_mon; + prev_state.persisted_monitor_id = mon_id; + 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 = <(BestBlock, 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 { @@ -1206,28 +1307,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()); @@ -1250,11 +1348,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()) }); @@ -1267,7 +1362,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, @@ -1277,7 +1372,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, @@ -1287,7 +1382,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, @@ -1319,30 +1414,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); @@ -1369,9 +1462,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]> = @@ -1387,82 +1480,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 = <(BestBlock, 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 = - <(BestBlock, 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; @@ -2211,24 +2229,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 { @@ -2404,7 +2410,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; } @@ -2420,7 +2426,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; } @@ -2436,7 +2442,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; } @@ -2511,13 +2517,13 @@ pub fn do_test(data: &[u8], out: Out) { }, // Sync node by 1 block to cover confirmation of a transaction. - 0xa8 => sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, Some(1)), - 0xa9 => sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, Some(1)), - 0xaa => sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, Some(1)), + 0xa8 => sync_with_chain_state(&mut nodes[0], &chain_state, Some(1)), + 0xa9 => sync_with_chain_state(&mut nodes[1], &chain_state, Some(1)), + 0xaa => 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 => sync_with_chain_state(&chain_state, &nodes[0], &mut node_height_a, None), - 0xac => sync_with_chain_state(&chain_state, &nodes[1], &mut node_height_b, None), - 0xad => sync_with_chain_state(&chain_state, &nodes[2], &mut node_height_c, None), + 0xab => sync_with_chain_state(&mut nodes[0], &chain_state, None), + 0xac => sync_with_chain_state(&mut nodes[1], &chain_state, None), + 0xad => sync_with_chain_state(&mut nodes[2], &chain_state, None), 0xb0 | 0xb1 | 0xb2 => { // Restart node A, picking among the in-flight `ChannelMonitor`s to use based on @@ -2532,11 +2538,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 @@ -2555,11 +2557,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 @@ -2574,11 +2572,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(), @@ -2861,24 +2855,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 de72b4912b1821a05563abb3bb3c61595aef10c9 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:32:31 +0200 Subject: [PATCH 06/19] Extract chanmon harness node action helpers --- fuzz/src/chanmon_consistency.rs | 303 ++++++++++++++++---------------- 1 file changed, 153 insertions(+), 150 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 670262c8642..c38f91aae3d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1079,6 +1079,147 @@ impl<'a> HarnessNode<'a> { } } +#[derive(Copy, Clone)] +enum MonitorUpdateSelector { + First, + Second, + Last, +} + +fn complete_monitor_update( + monitor: &Arc, chan_id: &ChannelId, selector: MonitorUpdateSelector, +) { + 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" + ); + 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 { + 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_monitor_updates(monitor: &Arc, chan_id: &ChannelId) { + 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; + } + } + } +} + +fn 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); + } +} + +fn splice_in(node: &HarnessNode<'_>, counterparty_node_id: &PublicKey, channel_id: &ChannelId) { + let wallet = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); + match node.splice_channel(channel_id, counterparty_node_id) { + Ok(funding_template) => { + let feerate = funding_template + .min_rbf_feerate() + .unwrap_or(node.fee_estimator.feerate_sat_per_kw()); + if let Ok(contribution) = funding_template.splice_in_sync( + Amount::from_sat(10_000), + feerate, + FeeRate::MAX, + &wallet, + ) { + 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 + ); + }, + } +} + +fn 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; + } + match node.splice_channel(channel_id, counterparty_node_id) { + Ok(funding_template) => { + let feerate = funding_template + .min_rbf_feerate() + .unwrap_or(node.fee_estimator.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(), + }]; + let wallet_sync = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); + if let Ok(contribution) = + funding_template.splice_out_sync(outputs, feerate, FeeRate::MAX, &wallet_sync) + { + 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 + ); + }, + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1414,24 +1555,6 @@ 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); @@ -2010,126 +2133,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_sync( - outputs, - feerate, - FeeRate::MAX, - &WalletSync::new(&node.wallet, Arc::clone(&node.logger)), - ) - }, - ); - }; - let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { let source = &nodes[source_idx]; @@ -2643,65 +2646,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); + complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::First); } }, 0xf1 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, &complete_second); + complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::Second); } }, 0xf2 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, &Vec::pop); + complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::Last); } }, 0xf4 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_first); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::First); } }, 0xf5 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_second); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Second); } }, 0xf6 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Last); } }, 0xf8 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_first); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::First); } }, 0xf9 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &complete_second); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Second); } }, 0xfa => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, &Vec::pop); + complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Last); } }, 0xfc => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &complete_first); + complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::First); } }, 0xfd => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &complete_second); + complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::Second); } }, 0xfe => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, &Vec::pop); + complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::Last); } }, From c3391b5a141fb3c86b47bbbd3d14bb2563404dcc Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:20:57 +0200 Subject: [PATCH 07/19] Extract chanmon harness node operations --- fuzz/src/chanmon_consistency.rs | 433 +++++++++++++++----------------- 1 file changed, 208 insertions(+), 225 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index c38f91aae3d..d9fa844828c 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -988,6 +988,22 @@ impl<'a> HarnessNode<'a> { *self.monitor.persister.update_ret.lock().unwrap() = 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(..) { @@ -1000,12 +1016,158 @@ 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) { + 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(), + }]; + let wallet_sync = WalletSync::new(&self.wallet, Arc::clone(&self.logger)); + if let Ok(contribution) = + funding_template.splice_out_sync(outputs, feerate, FeeRate::MAX, &wallet_sync) + { + 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, ) { @@ -1086,140 +1248,6 @@ enum MonitorUpdateSelector { Last, } -fn complete_monitor_update( - monitor: &Arc, chan_id: &ChannelId, selector: MonitorUpdateSelector, -) { - 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" - ); - 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 { - 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_monitor_updates(monitor: &Arc, chan_id: &ChannelId) { - 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; - } - } - } -} - -fn 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); - } -} - -fn splice_in(node: &HarnessNode<'_>, counterparty_node_id: &PublicKey, channel_id: &ChannelId) { - let wallet = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); - match node.splice_channel(channel_id, counterparty_node_id) { - Ok(funding_template) => { - let feerate = funding_template - .min_rbf_feerate() - .unwrap_or(node.fee_estimator.feerate_sat_per_kw()); - if let Ok(contribution) = funding_template.splice_in_sync( - Amount::from_sat(10_000), - feerate, - FeeRate::MAX, - &wallet, - ) { - 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 - ); - }, - } -} - -fn 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; - } - match node.splice_channel(channel_id, counterparty_node_id) { - Ok(funding_template) => { - let feerate = funding_template - .min_rbf_feerate() - .unwrap_or(node.fee_estimator.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(), - }]; - let wallet_sync = WalletSync::new(&node.wallet, Arc::clone(&node.logger)); - if let Ok(contribution) = - funding_template.splice_out_sync(outputs, feerate, FeeRate::MAX, &wallet_sync) - { - 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 - ); - }, - } -} - fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1556,9 +1584,9 @@ pub fn do_test(data: &[u8], out: Out) { nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); // 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); @@ -2241,22 +2269,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); } }, @@ -2412,82 +2440,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 => { @@ -2495,38 +2481,38 @@ 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 => sync_with_chain_state(&mut nodes[0], &chain_state, Some(1)), - 0xa9 => sync_with_chain_state(&mut nodes[1], &chain_state, Some(1)), - 0xaa => sync_with_chain_state(&mut nodes[2], &chain_state, Some(1)), + 0xa8 => nodes[0].sync_with_chain_state(&chain_state, Some(1)), + 0xa9 => nodes[1].sync_with_chain_state(&chain_state, Some(1)), + 0xaa => 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 => sync_with_chain_state(&mut nodes[0], &chain_state, None), - 0xac => sync_with_chain_state(&mut nodes[1], &chain_state, None), - 0xad => sync_with_chain_state(&mut nodes[2], &chain_state, None), + 0xab => nodes[0].sync_with_chain_state(&chain_state, None), + 0xac => nodes[1].sync_with_chain_state(&chain_state, None), + 0xad => nodes[2].sync_with_chain_state(&chain_state, None), 0xb0 | 0xb1 | 0xb2 => { // Restart node A, picking among the in-flight `ChannelMonitor`s to use based on @@ -2646,65 +2632,65 @@ pub fn do_test(data: &[u8], out: Out) { 0xf0 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::First); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf1 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::Second); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xf2 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[0].monitor, id, MonitorUpdateSelector::Last); + nodes[0].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xf4 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::First); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf5 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Second); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xf6 => { for id in &chan_ab_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Last); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xf8 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::First); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xf9 => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Second); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xfa => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[1].monitor, id, MonitorUpdateSelector::Last); + nodes[1].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, 0xfc => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::First); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::First); } }, 0xfd => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::Second); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Second); } }, 0xfe => { for id in &chan_bc_ids { - complete_monitor_update(&nodes[2].monitor, id, MonitorUpdateSelector::Last); + nodes[2].complete_monitor_update(id, MonitorUpdateSelector::Last); } }, @@ -2762,12 +2748,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) { @@ -2858,12 +2844,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 216f335418ed2d5c96e014070981777880383171 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 21:13:46 +0200 Subject: [PATCH 08/19] Introduce chanmon event queue bundle --- fuzz/src/chanmon_consistency.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index d9fa844828c..039b40c0e98 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1248,6 +1248,19 @@ 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 build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1608,10 +1621,8 @@ 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 EventQueues { ab: mut ab_events, ba: mut ba_events, bc: mut bc_events, cb: mut cb_events } = + EventQueues::new(); for node in &mut nodes { node.serialized_manager = node.encode(); From 5b85a342259b036f01fdef7929239681f0ec5f77 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 21:15:43 +0200 Subject: [PATCH 09/19] Construct chanmon event queues --- fuzz/src/chanmon_consistency.rs | 746 +++++++++++++++++++++----------- 1 file changed, 499 insertions(+), 247 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 039b40c0e98..fc8c18309a0 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, @@ -1613,16 +1612,13 @@ 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] }; - // 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 mut p_ctr: u64 = 0; - let mut peers_ab_disconnected = false; let mut peers_bc_disconnected = false; let EventQueues { ab: mut ab_events, ba: mut ba_events, bc: mut bc_events, cb: mut cb_events } = EventQueues::new(); + let mut p_ctr: u64 = 0; for node in &mut nodes { node.serialized_manager = node.encode(); @@ -1657,93 +1653,173 @@ 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) => { { + ($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 }; + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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"); } + 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); } + if push_a { + ba_events.push(event); + } else { + bc_events.push(event); + } } - } } + }}; } // While delivering messages, we select across three possible message selection processes @@ -1764,7 +1840,7 @@ pub fn do_test(data: &[u8], out: Out) { } macro_rules! process_msg_events { - ($node: expr, $corrupt_forward: expr, $limit_events: expr) => { { + ($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); @@ -1790,13 +1866,35 @@ pub fn do_test(data: &[u8], out: Out) { 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 } } => { + 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()); + 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); + 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 @@ -1804,42 +1902,105 @@ pub fn do_test(data: &[u8], out: Out) { // 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 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(); + 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); + 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); + 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); + 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()); + 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 - } }); + 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); + 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; } } @@ -1847,7 +2008,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1855,15 +2023,28 @@ pub fn do_test(data: &[u8], out: Out) { 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); + 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()); + 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); } } @@ -1871,7 +2052,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1879,7 +2067,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1887,7 +2082,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1895,7 +2097,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1903,7 +2112,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1911,7 +2127,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1919,7 +2142,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1927,7 +2157,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1935,7 +2172,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1943,7 +2187,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1951,7 +2202,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1959,7 +2217,14 @@ pub fn do_test(data: &[u8], out: Out) { 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()); + 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); } } @@ -1989,14 +2254,22 @@ pub fn do_test(data: &[u8], out: Out) { if $node == 1 { push_excess_b_events!(extra_ev.into_iter().chain(events_iter), None); } 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 { + ab_events.push(ev); + } + for event in events_iter { + ab_events.push(event); + } } 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 { + cb_events.push(ev); + } + for event in events_iter { + cb_events.push(event); + } } had_events - } } + }}; } macro_rules! process_msg_noret { @@ -2172,98 +2445,95 @@ 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); + macro_rules! send { + ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ + 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); + 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); + pending_payments.borrow_mut()[$source_idx].push(id); } succeeded - }; - let send_noret = |source_idx, dest_idx, dest_chan_id, amt, payment_ctr: &mut u64| { - send(source_idx, dest_idx, dest_chan_id, amt, payment_ctr); - }; + }}; + } - 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); - } - }; + macro_rules! send_noret { + ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ + send!($source_idx, $dest_idx, $dest_chan_id, $amt, $payment_ctr); + }}; + } - // 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); - } - }; + macro_rules! send_hop_noret { + ($source_idx: expr, $middle_idx: expr, $middle_chan_id: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ + 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); + } + }}; + } - // 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); - } - }; + macro_rules! send_mpp_direct { + ($source_idx: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr, $payment_ctr: expr) => {{ + 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); + } + }}; + } + + macro_rules! send_mpp_hop { + ($source_idx: expr, $middle_idx: expr, $middle_chan_ids: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr, $payment_ctr: expr) => {{ + 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()); @@ -2381,74 +2651,74 @@ 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 => 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), // 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 => send_mpp_direct!(0, 1, &chan_ab_ids, 1_000_000, &mut p_ctr), // 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 => send_mpp_hop!(0, 1, &chan_ab_ids, 2, &[chan_b_id], 1_000_000, &mut p_ctr), // 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 => send_mpp_hop!(0, 1, &chan_ab_ids, 2, &chan_bc_ids, 1_000_000, &mut p_ctr), // 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 => send_mpp_hop!(0, 1, &[chan_a_id], 2, &chan_bc_ids, 1_000_000, &mut p_ctr), // 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) + send_mpp_direct!(0, 1, &[chan_a_id, chan_a_id, chan_a_id], 1_000_000, &mut p_ctr) }, 0x80 => nodes[0].bump_fee_estimate(chan_type), @@ -2526,8 +2796,6 @@ pub fn do_test(data: &[u8], out: Out) { 0xad => nodes[2].sync_with_chain_state(&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. if !peers_ab_disconnected { nodes[1].peer_disconnected(nodes[0].get_our_node_id()); peers_ab_disconnected = true; @@ -2541,8 +2809,6 @@ pub fn do_test(data: &[u8], out: Out) { 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; @@ -2560,8 +2826,6 @@ pub fn do_test(data: &[u8], out: Out) { 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; @@ -2709,7 +2973,6 @@ pub fn do_test(data: &[u8], out: Out) { // 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 if peers_ab_disconnected { let init_1 = Init { features: nodes[1].init_features(), @@ -2751,13 +3014,14 @@ pub fn do_test(data: &[u8], out: Out) { 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"); + 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); @@ -2766,7 +3030,6 @@ pub fn do_test(data: &[u8], out: Out) { 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) { last_pass_no_updates = false; continue; @@ -2779,7 +3042,6 @@ pub fn do_test(data: &[u8], out: Out) { last_pass_no_updates = false; continue; } - // ...making sure any payments are claimed. if process_events!(0, false) { last_pass_no_updates = false; continue; @@ -2793,18 +3055,11 @@ pub fn do_test(data: &[u8], out: Out) { 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!(); @@ -2817,7 +3072,6 @@ 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(), @@ -2827,8 +3081,6 @@ pub fn do_test(data: &[u8], out: Out) { ); } - // 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| { @@ -2844,14 +3096,14 @@ 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 { assert!( - send(0, 1, chan_id, 10_000_000, &mut p_ctr) - || send(1, 0, chan_id, 10_000_000, &mut p_ctr) + 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 { assert!( - send(1, 2, chan_id, 10_000_000, &mut p_ctr) - || send(2, 1, chan_id, 10_000_000, &mut p_ctr) + send!(1, 2, chan_id, 10_000_000, &mut p_ctr) + || send!(2, 1, chan_id, 10_000_000, &mut p_ctr) ); } From 3214c819ab3885ebe9c1804ced74f2dfe03b210c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 18:05:43 +0200 Subject: [PATCH 10/19] Group chanmon event queue state --- fuzz/src/chanmon_consistency.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index fc8c18309a0..1b9afdc6052 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1616,8 +1616,8 @@ pub fn do_test(data: &[u8], out: Out) { let chan_b_id = chan_bc_ids[0]; let mut peers_ab_disconnected = false; let mut peers_bc_disconnected = false; - let EventQueues { ab: mut ab_events, ba: mut ba_events, bc: mut bc_events, cb: mut cb_events } = - EventQueues::new(); + let mut queues = EventQueues::new(); + let EventQueues { ab: ab_events, ba: ba_events, bc: bc_events, cb: cb_events } = &mut queues; let mut p_ctr: u64 = 0; for node in &mut nodes { @@ -1843,17 +1843,17 @@ pub fn do_test(data: &[u8], out: Out) { ($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); + mem::swap(&mut new_events, 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); + mem::swap(&mut new_events, ab_events); new_events } else { let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut cb_events); + mem::swap(&mut new_events, cb_events); new_events }; let mut new_events = Vec::new(); From 690c396b81d6a56aae7b72dd41b809178e2bbfdc Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:53:03 +0200 Subject: [PATCH 11/19] Group chanmon event queues --- fuzz/src/chanmon_consistency.rs | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 1b9afdc6052..3756aba6483 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1617,7 +1617,6 @@ pub fn do_test(data: &[u8], out: Out) { let mut peers_ab_disconnected = false; let mut peers_bc_disconnected = false; let mut queues = EventQueues::new(); - let EventQueues { ab: ab_events, ba: ba_events, bc: bc_events, cb: cb_events } = &mut queues; let mut p_ctr: u64 = 0; for node in &mut nodes { @@ -1651,7 +1650,7 @@ pub fn do_test(data: &[u8], out: Out) { } loop { - // Push any events from Node B onto ba_events and bc_events + // Push any events from Node B onto queues.ba and queues.bc macro_rules! push_excess_b_events { ($excess_events: expr, $expect_drop_node: expr) => {{ let a_id = nodes[0].get_our_node_id(); @@ -1814,9 +1813,9 @@ pub fn do_test(data: &[u8], out: Out) { _ => panic!("Unhandled message event {:?}", event), }; if push_a { - ba_events.push(event); + queues.ba.push(event); } else { - bc_events.push(event); + queues.bc.push(event); } } }}; @@ -1843,17 +1842,17 @@ pub fn do_test(data: &[u8], out: Out) { ($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, ba_events); - new_events.extend_from_slice(&bc_events[..]); - bc_events.clear(); + mem::swap(&mut new_events, &mut queues.ba); + new_events.extend_from_slice(&queues.bc[..]); + queues.bc.clear(); new_events } else if $node == 0 { let mut new_events = Vec::new(); - mem::swap(&mut new_events, ab_events); + mem::swap(&mut new_events, &mut queues.ab); new_events } else { let mut new_events = Vec::new(); - mem::swap(&mut new_events, cb_events); + mem::swap(&mut new_events, &mut queues.cb); new_events }; let mut new_events = Vec::new(); @@ -2255,17 +2254,17 @@ pub fn do_test(data: &[u8], out: Out) { push_excess_b_events!(extra_ev.into_iter().chain(events_iter), None); } else if $node == 0 { if let Some(ev) = extra_ev { - ab_events.push(ev); + queues.ab.push(ev); } for event in events_iter { - ab_events.push(event); + queues.ab.push(event); } } else { if let Some(ev) = extra_ev { - cb_events.push(ev); + queues.cb.push(ev); } for event in events_iter { - cb_events.push(event); + queues.cb.push(event); } } had_events @@ -2301,8 +2300,8 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].get_and_clear_pending_msg_events().drain(..), Some(0) ); - ab_events.clear(); - ba_events.clear(); + queues.ab.clear(); + queues.ba.clear(); } else { for event in nodes[2].get_and_clear_pending_msg_events() { match event { @@ -2324,8 +2323,8 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].get_and_clear_pending_msg_events().drain(..), Some(2) ); - bc_events.clear(); - cb_events.clear(); + queues.bc.clear(); + queues.cb.clear(); } }}; } @@ -2803,8 +2802,8 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].get_and_clear_pending_msg_events().drain(..), Some(0) ); - ab_events.clear(); - ba_events.clear(); + queues.ab.clear(); + queues.ba.clear(); } nodes[0].reload(v, &out, &router, chan_type); }, @@ -2813,15 +2812,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); }, @@ -2833,8 +2832,8 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].get_and_clear_pending_msg_events().drain(..), Some(2) ); - bc_events.clear(); - cb_events.clear(); + queues.bc.clear(); + queues.cb.clear(); } nodes[2].reload(v, &out, &router, chan_type); }, From d0ba4707b93d8b92d2dfecd85accf237290e07ce Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:39:32 +0200 Subject: [PATCH 12/19] Extract chanmon harness event queues --- fuzz/src/chanmon_consistency.rs | 414 ++++++++++++-------------------- 1 file changed, 159 insertions(+), 255 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 3756aba6483..8990fe49b78 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1258,6 +1258,144 @@ 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], + ) { + let a_id = nodes[0].our_node_id(); + let expect_drop_id = expect_drop_node.map(|id| nodes[id].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 { @@ -1650,177 +1788,6 @@ pub fn do_test(data: &[u8], out: Out) { } loop { - // Push any events from Node B onto queues.ba and queues.bc - 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 { - queues.ba.push(event); - } else { - queues.bc.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. @@ -1840,21 +1807,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 queues.ba); - new_events.extend_from_slice(&queues.bc[..]); - queues.bc.clear(); - new_events - } else if $node == 0 { - let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut queues.ab); - new_events - } else { - let mut new_events = Vec::new(); - mem::swap(&mut new_events, &mut queues.cb); - 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(); @@ -2251,21 +2204,18 @@ 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 { - queues.ab.push(ev); - } - for event in events_iter { - queues.ab.push(event); + queues.push_for_node(0, ev); } + queues.extend_for_node(0, events_iter); } else { if let Some(ev) = extra_ev { - queues.cb.push(ev); - } - for event in events_iter { - queues.cb.push(event); + queues.push_for_node(2, ev); } + queues.extend_for_node(2, events_iter); } had_events }}; @@ -2277,58 +2227,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) - ); - queues.ab.clear(); - queues.ba.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) - ); - queues.bc.clear(); - queues.cb.clear(); - } - }}; - } - macro_rules! process_events { ($node: expr, $fail: expr) => {{ // Multiple HTLCs can resolve for the same payment hash, so deduplicate @@ -2573,7 +2471,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 => { @@ -2581,7 +2481,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 => { @@ -2798,9 +2700,10 @@ 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, ); queues.ab.clear(); queues.ba.clear(); @@ -2828,9 +2731,10 @@ 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, ); queues.bc.clear(); queues.cb.clear(); From 4fa5aa2790fa20054f6170b35d102cf8c3ed8cd8 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:09:02 +0200 Subject: [PATCH 13/19] Extract chanmon harness message processing --- fuzz/src/chanmon_consistency.rs | 698 ++++++++++++-------------------- 1 file changed, 270 insertions(+), 428 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 8990fe49b78..2c017982e64 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -875,6 +875,19 @@ enum ChanType { ZeroFeeCommitments, } +#[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>, @@ -1613,6 +1626,255 @@ fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { } } +fn process_msg_events_impl( + node_idx: usize, corrupt_forward: bool, limit_events: ProcessMessages, + nodes: &[HarnessNode<'_>; 3], out: &Out, queues: &mut EventQueues, +) -> bool { + fn find_destination_node(nodes: &[HarnessNode<'_>; 3], node_id: &PublicKey) -> usize { + nodes + .iter() + .position(|node| node.our_node_id() == *node_id) + .expect("message destination should be a known harness node") + } + + 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 { + 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 { + 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 { .. } + | MessageSendEvent::BroadcastChannelUpdate { .. } => None, + _ => panic!("Unhandled message event {:?}", event), + } + } + + 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].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 +} + #[inline] pub fn do_test(data: &[u8], out: Out) { let router = FuzzRouter {}; @@ -1788,436 +2050,16 @@ pub fn do_test(data: &[u8], out: Out) { } 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 + process_msg_events_impl( + $node, + $corrupt_forward, + $limit_events, + &nodes, + &out, + &mut queues, + ) }}; } From ebef24995086836fb4a594bb863985ae932cbe08 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:07:32 +0200 Subject: [PATCH 14/19] Extract chanmon harness peer links --- fuzz/src/chanmon_consistency.rs | 326 +++++++++++++++----------------- 1 file changed, 152 insertions(+), 174 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 2c017982e64..a3a996d1b7e 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1366,6 +1366,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 => { @@ -1411,6 +1425,110 @@ 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: &mut [HarnessNode<'_>; 3], queues: &mut EventQueues) { + if self.disconnected { + return; + } + let node_a_id = nodes[self.node_a].our_node_id(); + let node_b_id = nodes[self.node_b].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: &mut [HarnessNode<'_>; 3]) { + if !self.disconnected { + return; + } + let node_a_id = nodes[self.node_a].our_node_id(); + let node_b_id = nodes[self.node_b].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: &mut [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].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; @@ -2012,10 +2130,10 @@ 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 chan_a_id = chan_ab_ids[0]; - let chan_b_id = chan_bc_ids[0]; - let mut peers_ab_disconnected = false; - let mut peers_bc_disconnected = false; + let mut ab_link = PeerLink::new(0, 1, chan_ab_ids); + let mut bc_link = PeerLink::new(1, 2, chan_bc_ids); + let chan_a_id = ab_link.first_channel_id(); + let chan_b_id = bc_link.first_channel_id(); let mut queues = EventQueues::new(); let mut p_ctr: u64 = 0; @@ -2288,80 +2406,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(&mut nodes, &mut queues), + 0x0d => bc_link.disconnect(&mut nodes, &mut queues), + 0x0e => ab_link.reconnect(&mut nodes), + 0x0f => bc_link.reconnect(&mut nodes), 0x10 => process_msg_noret!(0, true, ProcessMessages::AllMessages), 0x11 => process_msg_noret!(0, false, ProcessMessages::AllMessages), @@ -2539,48 +2607,16 @@ pub fn do_test(data: &[u8], out: Out) { 0xad => nodes[2].sync_with_chain_state(&chain_state, None), 0xb0 | 0xb1 | 0xb2 => { - 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, &mut nodes, &mut queues); nodes[0].reload(v, &out, &router, chan_type); }, 0xb3..=0xbb => { - 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, &mut nodes, &mut queues); + bc_link.disconnect_for_reload(1, &mut nodes, &mut queues); nodes[1].reload(v, &out, &router, chan_type); }, 0xbc | 0xbd | 0xbe => { - 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, &mut nodes, &mut queues); nodes[2].reload(v, &out, &router, chan_type); }, @@ -2651,103 +2687,51 @@ 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 => { // Test that no channel is in a stuck state where neither party can send funds even // after we resolve all pending events. - 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(&mut nodes); + bc_link.reconnect(&mut nodes); for op in SUPPORTED_SIGNER_OPS { nodes[0].keys_manager.enable_op_for_all_signers(op); @@ -2767,14 +2751,8 @@ pub fn do_test(data: &[u8], out: Out) { "It may take may iterations to settle the state, but it should not take forever" ); } - 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); if process_msg_events!(0, false, ProcessMessages::AllMessages) { last_pass_no_updates = false; continue; @@ -2839,13 +2817,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 c218677a98c9a771261d9906410faa3782f40c27 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 15:51:28 +0200 Subject: [PATCH 15/19] Extract chanmon harness payment helpers --- fuzz/src/chanmon_consistency.rs | 684 +++++++++++++++++--------------- 1 file changed, 373 insertions(+), 311 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index a3a996d1b7e..18e70c1ab90 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -569,7 +569,7 @@ type ChanMan<'a> = ChannelManager< #[inline] fn get_payment_secret_hash( dest: &ChanMan, payment_ctr: &mut u64, - payment_preimages: &RefCell>, + payment_preimages: &mut HashMap, ) -> (PaymentSecret, PaymentHash) { *payment_ctr += 1; let mut payment_preimage = PaymentPreimage([0; 32]); @@ -578,7 +578,7 @@ fn get_payment_secret_hash( 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()); + assert!(payment_preimages.insert(payment_hash, payment_preimage).is_none()); (payment_secret, payment_hash) } @@ -1529,6 +1529,167 @@ impl PeerLink { } } +struct PaymentTracker { + pending_payments: [Vec; 3], + resolved_payments: [HashMap>; 3], + claimed_payment_hashes: HashSet, + payment_preimages: HashMap, + payment_ctr: u64, +} + +impl PaymentTracker { + fn new() -> Self { + Self { + pending_payments: [Vec::new(), Vec::new(), Vec::new()], + resolved_payments: [new_hash_map(), new_hash_map(), new_hash_map()], + claimed_payment_hashes: HashSet::new(), + payment_preimages: new_hash_map(), + payment_ctr: 0, + } + } + + fn next_payment(&mut self, dest: &ChanMan) -> (PaymentSecret, PaymentHash, PaymentId) { + let (secret, hash) = + get_payment_secret_hash(dest, &mut self.payment_ctr, &mut self.payment_preimages); + let mut id = PaymentId([0; 32]); + id.0[0..8].copy_from_slice(&self.payment_ctr.to_ne_bytes()); + (secret, hash, id) + } + + fn send_direct( + &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 succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); + if succeeded { + self.pending_payments[source_idx].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 succeeded = send_hop_payment( + source, + middle, + middle_chan_id, + dest, + dest_chan_id, + amt, + secret, + hash, + id, + ); + if succeeded { + self.pending_payments[source_idx].push(id); + } + } + + 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 succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id); + if succeeded { + self.pending_payments[source_idx].push(id); + } + } + + 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); + let succeeded = send_mpp_hop_payment( + source, + middle, + middle_chan_ids, + dest, + dest_chan_ids, + amt, + secret, + hash, + id, + ); + if succeeded { + self.pending_payments[source_idx].push(id); + } + } + + 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 idx_opt = self.pending_payments[node_idx].iter().position(|id| *id == sent_id); + if let Some(idx) = idx_opt { + self.pending_payments[node_idx].remove(idx); + self.resolved_payments[node_idx].insert(sent_id, Some(payment_hash)); + } else { + assert!(self.resolved_payments[node_idx].contains_key(&sent_id)); + } + } + + fn mark_resolved_without_hash(&mut self, node_idx: usize, payment_id: PaymentId) { + let idx_opt = self.pending_payments[node_idx].iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + self.pending_payments[node_idx].remove(idx); + self.resolved_payments[node_idx].insert(payment_id, None); + } else if !self.resolved_payments[node_idx].contains_key(&payment_id) { + self.resolved_payments[node_idx].insert(payment_id, None); + } + } + + fn assert_all_resolved(&self) { + for (idx, pending) in self.pending_payments.iter().enumerate() { + assert!( + pending.is_empty(), + "Node {} has {} stuck pending payments after settling all state", + idx, + pending.len() + ); + } + } + + fn assert_claims_reported(&self) { + for hash in self.claimed_payment_hashes.iter() { + let found = self + .resolved_payments + .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 + ); + } + } +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1993,6 +2154,112 @@ fn process_msg_events_impl( had_events } +fn process_events_impl( + node_idx: usize, fail: bool, nodes: &[HarnessNode<'_>; 3], chain_state: &mut ChainState, + payments: &mut PaymentTracker, +) -> bool { + 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); + } + }, + events::Event::PaymentSent { payment_id, payment_hash, .. } => { + payments.mark_sent(node_idx, payment_id.unwrap(), payment_hash); + }, + events::Event::ProbeSuccessful { payment_id, .. } => { + payments.mark_resolved_without_hash(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.confirm_tx(splice_tx); + }, + events::Event::SpliceFailed { .. } => {}, + events::Event::DiscardFunding { + funding_info: events::FundingInfo::Contribution { .. }, + .. + } => {}, + _ => panic!("Unhandled event"), + } + } + while nodes[node_idx].needs_pending_htlc_processing() { + nodes[node_idx].process_pending_htlc_forwards(); + } + had_events +} + +fn process_all_events_impl( + nodes: &[HarnessNode<'_>; 3], out: &Out, ab_link: &PeerLink, bc_link: &PeerLink, + chain_state: &mut ChainState, payments: &mut PaymentTracker, queues: &mut EventQueues, +) { + 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" + ); + } + ab_link.complete_all_monitor_updates(nodes); + bc_link.complete_all_monitor_updates(nodes); + if process_msg_events_impl(0, false, ProcessMessages::AllMessages, nodes, out, queues) { + last_pass_no_updates = false; + continue; + } + if process_msg_events_impl(1, false, ProcessMessages::AllMessages, nodes, out, queues) { + last_pass_no_updates = false; + continue; + } + if process_msg_events_impl(2, false, ProcessMessages::AllMessages, nodes, out, queues) { + last_pass_no_updates = false; + continue; + } + if process_events_impl(0, false, nodes, chain_state, payments) { + last_pass_no_updates = false; + continue; + } + if process_events_impl(1, false, nodes, chain_state, payments) { + last_pass_no_updates = false; + continue; + } + if process_events_impl(2, false, nodes, chain_state, payments) { + last_pass_no_updates = false; + continue; + } + if last_pass_no_updates { + break; + } + last_pass_no_updates = true; + } +} + #[inline] pub fn do_test(data: &[u8], out: Out) { let router = FuzzRouter {}; @@ -2135,19 +2402,12 @@ 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 queues = EventQueues::new(); - let mut p_ctr: u64 = 0; + 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); @@ -2189,110 +2449,7 @@ pub fn do_test(data: &[u8], out: Out) { 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(); - 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); - } - } - }, - 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)); - } - }, - // Even though we don't explicitly send probes, because probes are - // detected based on hashing the payment hash+preimage, its 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)); - } - }, - 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); - } - }, - 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.confirm_tx(splice_tx); - }, - events::Event::SpliceFailed { .. } => {}, - events::Event::DiscardFunding { - funding_info: events::FundingInfo::Contribution { .. }, - .. - } => {}, - - _ => panic!("Unhandled event"), - } - } - while nodes[$node].needs_pending_htlc_processing() { - nodes[$node].process_pending_htlc_forwards(); - } - had_events + process_events_impl($node, $fail, &nodes, &mut chain_state, &mut payments) }}; } @@ -2303,92 +2460,48 @@ pub fn do_test(data: &[u8], out: Out) { } macro_rules! send { - ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ - 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); - } - succeeded + ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ + payments.send_direct(&nodes, $source_idx, $dest_idx, $dest_chan_id, $amt) }}; } macro_rules! send_noret { - ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ - send!($source_idx, $dest_idx, $dest_chan_id, $amt, $payment_ctr); + ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ + send!($source_idx, $dest_idx, $dest_chan_id, $amt); }}; } macro_rules! send_hop_noret { - ($source_idx: expr, $middle_idx: expr, $middle_chan_id: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr, $payment_ctr: expr) => {{ - 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, + ($source_idx: expr, $middle_idx: expr, $middle_chan_id: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ + payments.send_hop( + &nodes, + $source_idx, + $middle_idx, $middle_chan_id, - dest, + $dest_idx, $dest_chan_id, $amt, - secret, - hash, - id, ); - if succeeded { - pending_payments.borrow_mut()[$source_idx].push(id); - } }}; } macro_rules! send_mpp_direct { - ($source_idx: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr, $payment_ctr: expr) => {{ - 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); - } + ($source_idx: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ + payments.send_mpp_direct(&nodes, $source_idx, $dest_idx, $dest_chan_ids, $amt); }}; } macro_rules! send_mpp_hop { - ($source_idx: expr, $middle_idx: expr, $middle_chan_ids: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr, $payment_ctr: expr) => {{ - 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, + ($source_idx: expr, $middle_idx: expr, $middle_chan_ids: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ + payments.send_mpp_hop( + &nodes, + $source_idx, + $middle_idx, $middle_chan_ids, - dest, + $dest_idx, $dest_chan_ids, $amt, - secret, - hash, - id, ); - if succeeded { - pending_payments.borrow_mut()[$source_idx].push(id); - } }}; } @@ -2462,75 +2575,75 @@ 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 => send_noret!(0, 1, chan_a_id, 10_000_000), + 0x31 => send_noret!(1, 0, chan_a_id, 10_000_000), + 0x32 => send_noret!(1, 2, chan_b_id, 10_000_000), + 0x33 => send_noret!(2, 1, chan_b_id, 10_000_000), + 0x34 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10_000_000), + 0x35 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10_000_000), + + 0x38 => send_noret!(0, 1, chan_a_id, 1_000_000), + 0x39 => send_noret!(1, 0, chan_a_id, 1_000_000), + 0x3a => send_noret!(1, 2, chan_b_id, 1_000_000), + 0x3b => send_noret!(2, 1, chan_b_id, 1_000_000), + 0x3c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1_000_000), + 0x3d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 1_000_000), + + 0x40 => send_noret!(0, 1, chan_a_id, 100_000), + 0x41 => send_noret!(1, 0, chan_a_id, 100_000), + 0x42 => send_noret!(1, 2, chan_b_id, 100_000), + 0x43 => send_noret!(2, 1, chan_b_id, 100_000), + 0x44 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 100_000), + 0x45 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 100_000), + + 0x48 => send_noret!(0, 1, chan_a_id, 10_000), + 0x49 => send_noret!(1, 0, chan_a_id, 10_000), + 0x4a => send_noret!(1, 2, chan_b_id, 10_000), + 0x4b => send_noret!(2, 1, chan_b_id, 10_000), + 0x4c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10_000), + 0x4d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10_000), + + 0x50 => send_noret!(0, 1, chan_a_id, 1_000), + 0x51 => send_noret!(1, 0, chan_a_id, 1_000), + 0x52 => send_noret!(1, 2, chan_b_id, 1_000), + 0x53 => send_noret!(2, 1, chan_b_id, 1_000), + 0x54 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1_000), + 0x55 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 1_000), + + 0x58 => send_noret!(0, 1, chan_a_id, 100), + 0x59 => send_noret!(1, 0, chan_a_id, 100), + 0x5a => send_noret!(1, 2, chan_b_id, 100), + 0x5b => send_noret!(2, 1, chan_b_id, 100), + 0x5c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 100), + 0x5d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 100), + + 0x60 => send_noret!(0, 1, chan_a_id, 10), + 0x61 => send_noret!(1, 0, chan_a_id, 10), + 0x62 => send_noret!(1, 2, chan_b_id, 10), + 0x63 => send_noret!(2, 1, chan_b_id, 10), + 0x64 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10), + 0x65 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10), + + 0x68 => send_noret!(0, 1, chan_a_id, 1), + 0x69 => send_noret!(1, 0, chan_a_id, 1), + 0x6a => send_noret!(1, 2, chan_b_id, 1), + 0x6b => send_noret!(2, 1, chan_b_id, 1), + 0x6c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1), + 0x6d => send_hop_noret!(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 => send_mpp_direct!(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 => send_mpp_hop!(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 => { + send_mpp_hop!(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 => send_mpp_hop!(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) - }, + 0x74 => send_mpp_direct!(0, 1, &[chan_a_id, chan_a_id, chan_a_id], 1_000_000), 0x80 => nodes[0].bump_fee_estimate(chan_type), 0x81 => nodes[0].reset_fee_estimate(), @@ -2742,50 +2855,15 @@ 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" - ); - } - ab_link.complete_all_monitor_updates(&nodes); - bc_link.complete_all_monitor_updates(&nodes); - 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; - } - 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 { - break; - } - last_pass_no_updates = true; - } - }}; - } - - process_all_events!(); + process_all_events_impl( + &nodes, + &out, + &ab_link, + &bc_link, + &mut chain_state, + &mut payments, + &mut queues, + ); // 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 @@ -2793,41 +2871,25 @@ pub fn do_test(data: &[u8], out: Out) { for node in &nodes { node.timer_tick_occurred(); } - process_all_events!(); - - 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() - ); - } + process_all_events_impl( + &nodes, + &out, + &ab_link, + &bc_link, + &mut chain_state, + &mut payments, + &mut queues, + ); - 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_all_resolved(); + 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) - ); + assert!(send!(0, 1, chan_id, 10_000_000) || send!(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) - ); + assert!(send!(1, 2, chan_id, 10_000_000) || send!(2, 1, chan_id, 10_000_000)); } nodes[0].record_last_htlc_clear_fee(); From 24d92ea463f07bfc5452335c86e2f53c14b02220 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:46:44 +0200 Subject: [PATCH 16/19] Build chanmon harness setup --- fuzz/src/chanmon_consistency.rs | 574 +++++++++++++++++--------------- 1 file changed, 298 insertions(+), 276 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 18e70c1ab90..612c44a08b3 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -903,14 +903,6 @@ struct HarnessNode<'a> { last_htlc_clear_fee: u32, } -impl<'a> std::ops::Deref for HarnessNode<'a> { - type Target = ChanMan<'a>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - impl<'a> HarnessNode<'a> { fn build_loggers( node_id: u8, out: &Out, @@ -1253,13 +1245,6 @@ impl<'a> HarnessNode<'a> { } } -#[derive(Copy, Clone)] -enum MonitorUpdateSelector { - First, - Second, - Last, -} - struct EventQueues { ab: Vec, ba: Vec, @@ -1383,7 +1368,7 @@ impl EventQueues { 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() { + for event in nodes[0].node.get_and_clear_pending_msg_events() { match event { MessageSendEvent::UpdateHTLCs { .. } => {}, MessageSendEvent::SendRevokeAndACK { .. } => {}, @@ -1399,10 +1384,14 @@ impl EventQueues { _ => panic!("Unhandled message event"), } } - self.route_from_middle(nodes[1].get_and_clear_pending_msg_events(), Some(0), nodes); + self.route_from_middle( + nodes[1].node.get_and_clear_pending_msg_events(), + Some(0), + nodes, + ); }, 2 => { - for event in nodes[2].get_and_clear_pending_msg_events() { + for event in nodes[2].node.get_and_clear_pending_msg_events() { match event { MessageSendEvent::UpdateHTLCs { .. } => {}, MessageSendEvent::SendRevokeAndACK { .. } => {}, @@ -1418,7 +1407,11 @@ impl EventQueues { _ => panic!("Unhandled message event"), } } - self.route_from_middle(nodes[1].get_and_clear_pending_msg_events(), Some(2), nodes); + self.route_from_middle( + nodes[1].node.get_and_clear_pending_msg_events(), + Some(2), + nodes, + ); }, _ => panic!("unsupported disconnected edge"), } @@ -1467,8 +1460,8 @@ impl PeerLink { } let node_a_id = nodes[self.node_a].our_node_id(); let node_b_id = nodes[self.node_b].our_node_id(); - nodes[self.node_a].peer_disconnected(node_b_id); - nodes[self.node_b].peer_disconnected(node_a_id); + nodes[self.node_a].node.peer_disconnected(node_b_id); + nodes[self.node_b].node.peer_disconnected(node_a_id); self.disconnected = true; let edge_node = if self.node_a == 1 { self.node_b @@ -1488,17 +1481,17 @@ impl PeerLink { let node_a_id = nodes[self.node_a].our_node_id(); let node_b_id = nodes[self.node_b].our_node_id(); let init_b = Init { - features: nodes[self.node_b].init_features(), + features: nodes[self.node_b].node.init_features(), networks: None, remote_network_address: None, }; - nodes[self.node_a].peer_connected(node_b_id, &init_b, true).unwrap(); + nodes[self.node_a].node.peer_connected(node_b_id, &init_b, true).unwrap(); let init_a = Init { - features: nodes[self.node_a].init_features(), + features: nodes[self.node_a].node.init_features(), networks: None, remote_network_address: None, }; - nodes[self.node_b].peer_connected(node_a_id, &init_a, false).unwrap(); + nodes[self.node_b].node.peer_connected(node_a_id, &init_a, false).unwrap(); self.disconnected = false; } @@ -1513,38 +1506,45 @@ impl PeerLink { let remaining_node = if restarted_node == self.node_a { self.node_b } else { self.node_a }; let restarted_node_id = nodes[restarted_node].our_node_id(); - nodes[remaining_node].peer_disconnected(restarted_node_id); + nodes[remaining_node].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(), + nodes[1].node.get_and_clear_pending_msg_events(), Some(restarted_node), nodes, ); } else { - nodes[remaining_node].get_and_clear_pending_msg_events(); + nodes[remaining_node].node.get_and_clear_pending_msg_events(); } queues.clear_link(self); } } +#[derive(Copy, Clone)] +enum MonitorUpdateSelector { + First, + Second, + Last, +} + struct PaymentTracker { + payment_ctr: u64, pending_payments: [Vec; 3], resolved_payments: [HashMap>; 3], claimed_payment_hashes: HashSet, payment_preimages: HashMap, - payment_ctr: u64, } impl PaymentTracker { fn new() -> Self { Self { + payment_ctr: 0, pending_payments: [Vec::new(), Vec::new(), Vec::new()], resolved_payments: [new_hash_map(), new_hash_map(), new_hash_map()], claimed_payment_hashes: HashSet::new(), payment_preimages: new_hash_map(), - payment_ctr: 0, } } @@ -1560,8 +1560,8 @@ impl PaymentTracker { &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 source = &nodes[source_idx].node; + let dest = &nodes[dest_idx].node; let (secret, hash, id) = self.next_payment(dest); let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); if succeeded { @@ -1574,9 +1574,9 @@ impl PaymentTracker { &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 source = &nodes[source_idx].node; + let middle = &nodes[middle_idx].node; + let dest = &nodes[dest_idx].node; let (secret, hash, id) = self.next_payment(dest); let succeeded = send_hop_payment( source, @@ -1598,8 +1598,8 @@ impl PaymentTracker { &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 source = &nodes[source_idx].node; + let dest = &nodes[dest_idx].node; let (secret, hash, id) = self.next_payment(dest); let succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id); if succeeded { @@ -1611,9 +1611,9 @@ impl PaymentTracker { &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 source = &nodes[source_idx].node; + let middle = &nodes[middle_idx].node; + let dest = &nodes[dest_idx].node; let (secret, hash, id) = self.next_payment(dest); let succeeded = send_mpp_hop_payment( source, @@ -1633,13 +1633,13 @@ impl PaymentTracker { fn claim_payment(&mut self, node: &HarnessNode<'_>, payment_hash: PaymentHash, fail: bool) { if fail { - node.fail_htlc_backwards(&payment_hash); + node.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); + node.node.claim_funds(payment_preimage); self.claimed_payment_hashes.insert(payment_hash); } } @@ -1690,6 +1690,18 @@ 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, + read_pos: usize, +} + fn build_node_config(chan_type: ChanType) -> UserConfig { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1713,9 +1725,9 @@ fn build_node_config(chan_type: ChanType) -> UserConfig { } 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); + assert_eq!(nodes[0].node.list_channels().len(), 3); + assert_eq!(nodes[1].node.list_channels().len(), 6); + assert_eq!(nodes[2].node.list_channels().len(), 3); assert!(nodes[0].broadcaster.txn_broadcasted.borrow().is_empty()); assert!(nodes[1].broadcaster.txn_broadcasted.borrow().is_empty()); @@ -1905,6 +1917,140 @@ fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { } } +impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { + fn new(data: &[u8], out: Out, router: &'a FuzzRouter) -> Self { + 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 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()) }); + + 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(&nodes[0].node, &nodes[1].node); + connect_peers(&nodes[1].node, &nodes[2].node); + + 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); + 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); + + nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); + nodes[2].broadcaster.txn_broadcasted.borrow_mut().clear(); + + 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 = { + let node_a_chans = nodes[0].node.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 = { + let node_c_chans = nodes[2].node.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.node.encode(); + } + + Self { + out, + chan_type, + 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(), + read_pos: 1, + } + } +} + fn process_msg_events_impl( node_idx: usize, corrupt_forward: bool, limit_events: ProcessMessages, nodes: &[HarnessNode<'_>; 3], out: &Out, queues: &mut EventQueues, @@ -1939,12 +2085,12 @@ fn process_msg_events_impl( corrupt_forward: bool, ) { if !corrupt_forward { - dest.handle_update_add_htlc(source_node_id, update_add); + dest.node.handle_update_add_htlc(source_node_id, update_add); } else { 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); + dest.node.handle_update_add_htlc(source_node_id, &new_msg); } } @@ -1974,19 +2120,19 @@ fn process_msg_events_impl( || !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); + dest.node.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); + dest.node.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); + dest.node.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); + dest.node.handle_update_fee(source_node_id, &msg); } if limit_events != ProcessMessages::AllMessages && processed_change { return Some(MessageSendEvent::UpdateHTLCs { @@ -2003,7 +2149,7 @@ fn process_msg_events_impl( }); } log_msg_delivery(node_idx, dest_idx, "commitment_signed", out); - dest.handle_commitment_signed_batch_test(source_node_id, &commitment_signed); + dest.node.handle_commitment_signed_batch_test(source_node_id, &commitment_signed); None } @@ -2027,78 +2173,78 @@ fn process_msg_events_impl( }, 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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.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); + nodes[dest_idx].node.handle_splice_locked(source_node_id, msg); None }, MessageSendEvent::HandleError { ref action, .. } => { @@ -2116,7 +2262,7 @@ fn process_msg_events_impl( 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(); + new_events = nodes[node_idx].node.get_and_clear_pending_msg_events(); } let mut had_events = false; let source_node_id = nodes[node_idx].our_node_id(); @@ -2159,7 +2305,7 @@ fn process_events_impl( payments: &mut PaymentTracker, ) -> bool { let mut claim_set = new_hash_map(); - let mut events = nodes[node_idx].get_and_clear_pending_events(); + let mut events = nodes[node_idx].node.get_and_clear_pending_events(); let had_events = !events.is_empty(); for event in events.drain(..) { match event { @@ -2192,6 +2338,7 @@ fn process_events_impl( } => { let signed_tx = nodes[node_idx].wallet.sign_tx(unsigned_transaction).unwrap(); nodes[node_idx] + .node .funding_transaction_signed(&channel_id, &counterparty_node_id, signed_tx) .unwrap(); }, @@ -2210,8 +2357,8 @@ fn process_events_impl( _ => panic!("Unhandled event"), } } - while nodes[node_idx].needs_pending_htlc_processing() { - nodes[node_idx].process_pending_htlc_forwards(); + while nodes[node_idx].node.needs_pending_htlc_processing() { + nodes[node_idx].node.process_pending_htlc_forwards(); } had_events } @@ -2263,150 +2410,19 @@ fn process_all_events_impl( #[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 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, - 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 in `test_return`. - 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); + let Harness { + out, + chan_type, + mut chain_state, + mut nodes, + mut ab_link, + mut bc_link, + mut queues, + mut payments, + mut read_pos, + } = Harness::new(data, out, &router); let chan_a_id = ab_link.first_channel_id(); let chan_b_id = bc_link.first_channel_id(); - let mut queues = EventQueues::new(); - let mut payments = PaymentTracker::new(); - - for node in &mut nodes { - node.serialized_manager = node.encode(); - } macro_rules! test_return { () => {{ @@ -2415,18 +2431,6 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - 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 { macro_rules! process_msg_events { ($node: expr, $corrupt_forward: expr, $limit_events: expr) => {{ @@ -2505,7 +2509,11 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - let v = get_slice!(1)[0]; + if data.len() < read_pos + 1 { + test_return!(); + } + 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 @@ -2654,59 +2662,67 @@ pub fn do_test(data: &[u8], out: Out) { 0xa0 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[1].get_our_node_id(); + let cp_node_id = nodes[1].our_node_id(); nodes[0].splice_in(&cp_node_id, &chan_a_id); }, 0xa1 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[0].get_our_node_id(); + let cp_node_id = nodes[0].our_node_id(); nodes[1].splice_in(&cp_node_id, &chan_a_id); }, 0xa2 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[2].get_our_node_id(); + let cp_node_id = nodes[2].our_node_id(); nodes[1].splice_in(&cp_node_id, &chan_b_id); }, 0xa3 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[1].get_our_node_id(); + let cp_node_id = nodes[1].our_node_id(); nodes[2].splice_in(&cp_node_id, &chan_b_id); }, 0xa4 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[1].get_our_node_id(); + let cp_node_id = nodes[1].our_node_id(); nodes[0].splice_out(&cp_node_id, &chan_a_id); }, 0xa5 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[0].get_our_node_id(); + let cp_node_id = nodes[0].our_node_id(); nodes[1].splice_out(&cp_node_id, &chan_a_id); }, 0xa6 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[2].get_our_node_id(); + let cp_node_id = nodes[2].our_node_id(); nodes[1].splice_out(&cp_node_id, &chan_b_id); }, 0xa7 => { if !cfg!(splicing) { - test_return!(); + assert_test_invariants(&nodes); + return; } - let cp_node_id = nodes[1].get_our_node_id(); + let cp_node_id = nodes[1].our_node_id(); nodes[2].splice_out(&cp_node_id, &chan_b_id); }, @@ -2720,15 +2736,21 @@ pub fn do_test(data: &[u8], out: Out) { 0xad => nodes[2].sync_with_chain_state(&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, &mut 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. ab_link.disconnect_for_reload(1, &mut nodes, &mut queues); bc_link.disconnect_for_reload(1, &mut 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. bc_link.disconnect_for_reload(2, &mut nodes, &mut queues); nodes[2].reload(v, &out, &router, chan_type); }, @@ -2740,103 +2762,103 @@ pub fn do_test(data: &[u8], out: Out) { nodes[0] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - nodes[0].signer_unblocked(None); + nodes[0].node.signer_unblocked(None); }, 0xc4 => { 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((nodes[0].our_node_id(), chan_a_id)); + nodes[1].node.signer_unblocked(filter); }, 0xc5 => { 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((nodes[2].our_node_id(), chan_b_id)); + nodes[1].node.signer_unblocked(filter); }, 0xc6 => { nodes[2] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - nodes[2].signer_unblocked(None); + nodes[2].node.signer_unblocked(None); }, 0xc7 => { nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[0].signer_unblocked(None); + nodes[0].node.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); + let filter = Some((nodes[0].our_node_id(), chan_a_id)); + nodes[1].node.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); + let filter = Some((nodes[2].our_node_id(), chan_b_id)); + nodes[1].node.signer_unblocked(filter); }, 0xca => { nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[2].signer_unblocked(None); + nodes[2].node.signer_unblocked(None); }, 0xcb => { nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[0].signer_unblocked(None); + nodes[0].node.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); + let filter = Some((nodes[0].our_node_id(), chan_a_id)); + nodes[1].node.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); + let filter = Some((nodes[2].our_node_id(), chan_b_id)); + nodes[1].node.signer_unblocked(filter); }, 0xce => { nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[2].signer_unblocked(None); + nodes[2].node.signer_unblocked(None); }, 0xf0 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::First) + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::First); }, 0xf1 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Second) + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Second); }, 0xf2 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Last) + ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Last); }, 0xf4 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First); }, 0xf5 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second); }, 0xf6 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) + ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last); }, 0xf8 => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First) + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::First); }, 0xf9 => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second) + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second); }, 0xfa => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last) + bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last); }, 0xfc => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::First) + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::First); }, 0xfd => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Second) + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Second); }, 0xfe => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Last) + bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Last); }, 0xff => { @@ -2851,9 +2873,9 @@ pub fn do_test(data: &[u8], out: Out) { 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); + nodes[0].node.signer_unblocked(None); + nodes[1].node.signer_unblocked(None); + nodes[2].node.signer_unblocked(None); process_all_events_impl( &nodes, @@ -2869,7 +2891,7 @@ pub fn do_test(data: &[u8], out: Out) { // 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(); + node.node.timer_tick_occurred(); } process_all_events_impl( &nodes, From 2ee3f47f4627ce1374abf64c54e16724bc4c50e5 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:47:33 +0200 Subject: [PATCH 17/19] Extract chanmon harness setup state --- fuzz/src/chanmon_consistency.rs | 331 +++++++++++++++++--------------- 1 file changed, 171 insertions(+), 160 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 612c44a08b3..284d18bf5fb 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1734,190 +1734,201 @@ fn assert_test_invariants(nodes: &[HarnessNode<'_>; 3]) { 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 }; - 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: &HarnessNode<'_>, dest: &HarnessNode<'_>, chan_id: i32, trusted_open: bool, - trusted_accept: bool, chain_state: &mut ChainState, -) { - if trusted_open { - source - .node - .create_channel_to_trusted_peer_0reserve(dest.our_node_id(), 100_000, 42, 0, None, None) - .unwrap(); - } else { - source.node.create_channel(dest.our_node_id(), 100_000, 42, 0, None, None).unwrap(); - } - let open_channel = { - let events = source.node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - if let MessageSendEvent::SendOpenChannel { ref msg, .. } = events[0] { - msg.clone() +impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { + 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: &HarnessNode<'_>, dest: &HarnessNode<'_>, chan_id: i32, trusted_open: bool, + trusted_accept: bool, chain_state: &mut ChainState, + ) { + if trusted_open { + source + .node + .create_channel_to_trusted_peer_0reserve( + dest.our_node_id(), + 100_000, + 42, + 0, + None, + None, + ) + .unwrap(); } else { - panic!("Wrong event type"); + source.node.create_channel(dest.our_node_id(), 100_000, 42, 0, None, None).unwrap(); } - }; + let open_channel = { + let events = source.node.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.node.handle_open_channel(source.our_node_id(), &open_channel); - let accept_channel = { - let events = dest.node.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] + dest.node.handle_open_channel(source.our_node_id(), &open_channel); + let accept_channel = { + let events = dest.node.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.node + .accept_inbound_channel_from_trusted_peer( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + TrustedChannelFeatures::ZeroReserve, + None, + ) + .unwrap(); + } else { + dest.node + .accept_inbound_channel( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + None, + ) + .unwrap(); + } + } else { + panic!("Wrong event type"); + } + let events = dest.node.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.node.handle_accept_channel(dest.our_node_id(), &accept_channel); { - 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.node - .accept_inbound_channel_from_trusted_peer( + let mut events = source.node.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 + .node + .funding_transaction_generated( temporary_channel_id, - counterparty_node_id, - user_channel_id, - TrustedChannelFeatures::ZeroReserve, - None, + dest.our_node_id(), + tx.clone(), ) .unwrap(); + chain_state.confirm_tx(tx); } else { - dest.node - .accept_inbound_channel( - temporary_channel_id, - counterparty_node_id, - user_channel_id, - None, - ) - .unwrap(); + panic!("Wrong event type"); } - } else { - panic!("Wrong event type"); } - let events = dest.node.get_and_clear_pending_msg_events(); + + let funding_created = { + let events = source.node.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.node.handle_funding_created(source.our_node_id(), &funding_created); + dest.complete_all_pending_monitor_updates(); + + let (funding_signed, channel_id) = { + let events = dest.node.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.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); - if let MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] { - msg.clone() + if let events::Event::ChannelPending { ref counterparty_node_id, .. } = events[0] { + assert_eq!(counterparty_node_id, &source.our_node_id()); } else { panic!("Wrong event type"); } - }; - source.node.handle_accept_channel(dest.our_node_id(), &accept_channel); - { - let mut events = source.node.get_and_clear_pending_events(); + source.node.handle_funding_signed(dest.our_node_id(), &funding_signed); + source.complete_all_pending_monitor_updates(); + + let events = source.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); - if let events::Event::FundingGenerationReady { - temporary_channel_id, - channel_value_satoshis, - output_script, + if let events::Event::ChannelPending { + ref counterparty_node_id, + channel_id: ref event_channel_id, .. - } = events.pop().unwrap() + } = events[0] { - 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 - .node - .funding_transaction_generated(temporary_channel_id, dest.our_node_id(), tx.clone()) - .unwrap(); - chain_state.confirm_tx(tx); + assert_eq!(counterparty_node_id, &dest.our_node_id()); + assert_eq!(*event_channel_id, channel_id); } else { panic!("Wrong event type"); } } - let funding_created = { - let events = source.node.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.node.handle_funding_created(source.our_node_id(), &funding_created); - dest.complete_all_pending_monitor_updates(); - - let (funding_signed, channel_id) = { - let events = dest.node.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"); + fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { + let mut node_events = Vec::new(); + for node in nodes.iter() { + node_events.push(node.node.get_and_clear_pending_msg_events()); } - }; - let events = dest.node.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.our_node_id()); - } else { - panic!("Wrong event type"); - } - - source.node.handle_funding_signed(dest.our_node_id(), &funding_signed); - source.complete_all_pending_monitor_updates(); - - let events = source.node.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.our_node_id()); - assert_eq!(*event_channel_id, channel_id); - } else { - panic!("Wrong event type"); - } -} - -fn lock_fundings(nodes: &[HarnessNode<'_>; 3]) { - let mut node_events = Vec::new(); - for node in nodes.iter() { - node_events.push(node.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.our_node_id() == *node_id { - node.node.handle_channel_ready(nodes[idx].our_node_id(), msg); + 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.our_node_id() == *node_id { + node.node.handle_channel_ready(nodes[idx].our_node_id(), msg); + } } + } else { + panic!("Wrong event type"); } - } else { - panic!("Wrong event type"); } } - } - for node in nodes.iter() { - let events = node.node.get_and_clear_pending_msg_events(); - for event in events { - if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { - } else { - panic!("Wrong event type"); + for node in nodes.iter() { + let events = node.node.get_and_clear_pending_msg_events(); + for event in events { + if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { + } else { + panic!("Wrong event type"); + } } } } -} -impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { fn new(data: &[u8], out: Out, router: &'a FuzzRouter) -> Self { let config_byte = if !data.is_empty() { data[0] } else { 0 }; let chan_type = match (config_byte >> 3) & 0b11 { @@ -2004,15 +2015,15 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { ]; let mut chain_state = ChainState::new(); - connect_peers(&nodes[0].node, &nodes[1].node); - connect_peers(&nodes[1].node, &nodes[2].node); + Self::connect_peers(&nodes[0].node, &nodes[1].node); + Self::connect_peers(&nodes[1].node, &nodes[2].node); - 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); - 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); + Self::make_channel(&nodes[0], &nodes[1], 1, false, false, &mut chain_state); + Self::make_channel(&nodes[0], &nodes[1], 2, true, true, &mut chain_state); + Self::make_channel(&nodes[0], &nodes[1], 3, false, true, &mut chain_state); + Self::make_channel(&nodes[1], &nodes[2], 4, false, true, &mut chain_state); + Self::make_channel(&nodes[1], &nodes[2], 5, true, false, &mut chain_state); + Self::make_channel(&nodes[1], &nodes[2], 6, false, false, &mut chain_state); nodes[0].broadcaster.txn_broadcasted.borrow_mut().clear(); nodes[1].broadcaster.txn_broadcasted.borrow_mut().clear(); @@ -2022,7 +2033,7 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { nodes[1].sync_with_chain_state(&chain_state, None); nodes[2].sync_with_chain_state(&chain_state, None); - lock_fundings(&nodes); + Self::lock_fundings(&nodes); let chan_ab_ids = { let node_a_chans = nodes[0].node.list_usable_channels(); From 012e4798fd1a0dd63ae70dbdba09307659d94918 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 17:03:43 +0200 Subject: [PATCH 18/19] Wrap chanmon consistency state in Harness --- fuzz/src/chanmon_consistency.rs | 457 ++++++++++++++++++++------------ 1 file changed, 281 insertions(+), 176 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 284d18bf5fb..25700201588 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -1690,7 +1690,8 @@ impl PaymentTracker { } } -struct Harness<'a, Out: Output + MaybeSend + MaybeSync> { +struct Harness<'a, 'd, Out: Output + MaybeSend + MaybeSync> { + data: &'d [u8], out: Out, chan_type: ChanType, chain_state: ChainState, @@ -1734,7 +1735,7 @@ fn assert_test_invariants(nodes: &[HarnessNode<'_>; 3]) { assert!(nodes[2].broadcaster.txn_broadcasted.borrow().is_empty()); } -impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { +impl<'a, 'd, Out: Output + MaybeSend + MaybeSync> Harness<'a, 'd, Out> { fn connect_peers(source: &ChanMan<'_>, dest: &ChanMan<'_>) { let init_dest = Init { features: dest.init_features(), networks: None, remote_network_address: None }; @@ -1929,7 +1930,7 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { } } - fn new(data: &[u8], out: Out, router: &'a FuzzRouter) -> Self { + fn new(data: &'d [u8], out: Out, router: &'a FuzzRouter) -> Self { let config_byte = if !data.is_empty() { data[0] } else { 0 }; let chan_type = match (config_byte >> 3) & 0b11 { 0 => ChanType::Legacy, @@ -2049,6 +2050,7 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { } Self { + data, out, chan_type, chain_state, @@ -2060,6 +2062,34 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> { read_pos: 1, } } + + fn chan_a_id(&self) -> ChannelId { + self.ab_link.first_channel_id() + } + + fn chan_b_id(&self) -> ChannelId { + self.bc_link.first_channel_id() + } + + fn next_input_byte(&mut self) -> Option { + if self.data.len() < self.read_pos + 1 { + None + } else { + let value = self.data[self.read_pos]; + self.read_pos += 1; + Some(value) + } + } + + fn finish(&self) { + assert_test_invariants(&self.nodes); + } + + fn refresh_serialized_managers(&mut self) { + for node in &mut self.nodes { + node.refresh_serialized_manager(); + } + } } fn process_msg_events_impl( @@ -2421,23 +2451,13 @@ fn process_all_events_impl( #[inline] pub fn do_test(data: &[u8], out: Out) { let router = FuzzRouter {}; - let Harness { - out, - chan_type, - mut chain_state, - mut nodes, - mut ab_link, - mut bc_link, - mut queues, - mut payments, - mut read_pos, - } = Harness::new(data, out, &router); - let chan_a_id = ab_link.first_channel_id(); - let chan_b_id = bc_link.first_channel_id(); + let mut harness = Harness::new(data, out, &router); + let chan_a_id = harness.chan_a_id(); + let chan_b_id = harness.chan_b_id(); macro_rules! test_return { () => {{ - assert_test_invariants(&nodes); + harness.finish(); return; }}; } @@ -2449,9 +2469,9 @@ pub fn do_test(data: &[u8], out: Out) { $node, $corrupt_forward, $limit_events, - &nodes, - &out, - &mut queues, + &harness.nodes, + &harness.out, + &mut harness.queues, ) }}; } @@ -2464,7 +2484,13 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! process_events { ($node: expr, $fail: expr) => {{ - process_events_impl($node, $fail, &nodes, &mut chain_state, &mut payments) + process_events_impl( + $node, + $fail, + &harness.nodes, + &mut harness.chain_state, + &mut harness.payments, + ) }}; } @@ -2476,7 +2502,13 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! send { ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ - payments.send_direct(&nodes, $source_idx, $dest_idx, $dest_chan_id, $amt) + harness.payments.send_direct( + &harness.nodes, + $source_idx, + $dest_idx, + $dest_chan_id, + $amt, + ) }}; } @@ -2488,8 +2520,8 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! send_hop_noret { ($source_idx: expr, $middle_idx: expr, $middle_chan_id: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ - payments.send_hop( - &nodes, + harness.payments.send_hop( + &harness.nodes, $source_idx, $middle_idx, $middle_chan_id, @@ -2502,14 +2534,20 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! send_mpp_direct { ($source_idx: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ - payments.send_mpp_direct(&nodes, $source_idx, $dest_idx, $dest_chan_ids, $amt); + harness.payments.send_mpp_direct( + &harness.nodes, + $source_idx, + $dest_idx, + $dest_chan_ids, + $amt, + ); }}; } macro_rules! send_mpp_hop { ($source_idx: expr, $middle_idx: expr, $middle_chan_ids: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ - payments.send_mpp_hop( - &nodes, + harness.payments.send_mpp_hop( + &harness.nodes, $source_idx, $middle_idx, $middle_chan_ids, @@ -2520,48 +2558,46 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - if data.len() < read_pos + 1 { - test_return!(); - } - let v = data[read_pos]; - read_pos += 1; - out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); + let v = if let Some(value) = harness.next_input_byte() { value } else { test_return!() }; + 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(&mut nodes, &mut queues), - 0x0d => bc_link.disconnect(&mut nodes, &mut queues), - 0x0e => ab_link.reconnect(&mut nodes), - 0x0f => bc_link.reconnect(&mut nodes), + 0x0c => harness.ab_link.disconnect(&mut harness.nodes, &mut harness.queues), + 0x0d => harness.bc_link.disconnect(&mut harness.nodes, &mut harness.queues), + 0x0e => harness.ab_link.reconnect(&mut harness.nodes), + 0x0f => harness.bc_link.reconnect(&mut harness.nodes), 0x10 => process_msg_noret!(0, true, ProcessMessages::AllMessages), 0x11 => process_msg_noret!(0, false, ProcessMessages::AllMessages), @@ -2652,289 +2688,358 @@ pub fn do_test(data: &[u8], out: Out) { // MPP payments // 0x70: direct MPP from 0 to 1 (multi A-B channels) - 0x70 => send_mpp_direct!(0, 1, ab_link.channel_ids(), 1_000_000), + 0x70 => send_mpp_direct!(0, 1, harness.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, ab_link.channel_ids(), 2, &[chan_b_id], 1_000_000), + 0x71 => send_mpp_hop!(0, 1, harness.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, ab_link.channel_ids(), 2, bc_link.channel_ids(), 1_000_000) + send_mpp_hop!( + 0, + 1, + harness.ab_link.channel_ids(), + 2, + harness.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, bc_link.channel_ids(), 1_000_000), + 0x73 => send_mpp_hop!(0, 1, &[chan_a_id], 2, harness.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), - 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) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[1].our_node_id(); - nodes[0].splice_in(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[1].our_node_id(); + harness.nodes[0].splice_in(&cp_node_id, &harness.chan_a_id()); }, 0xa1 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[0].our_node_id(); - nodes[1].splice_in(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[0].our_node_id(); + harness.nodes[1].splice_in(&cp_node_id, &harness.chan_a_id()); }, 0xa2 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[2].our_node_id(); - nodes[1].splice_in(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[2].our_node_id(); + harness.nodes[1].splice_in(&cp_node_id, &harness.chan_b_id()); }, 0xa3 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[1].our_node_id(); - nodes[2].splice_in(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[1].our_node_id(); + harness.nodes[2].splice_in(&cp_node_id, &harness.chan_b_id()); }, 0xa4 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[1].our_node_id(); - nodes[0].splice_out(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[1].our_node_id(); + harness.nodes[0].splice_out(&cp_node_id, &harness.chan_a_id()); }, 0xa5 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[0].our_node_id(); - nodes[1].splice_out(&cp_node_id, &chan_a_id); + let cp_node_id = harness.nodes[0].our_node_id(); + harness.nodes[1].splice_out(&cp_node_id, &harness.chan_a_id()); }, 0xa6 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[2].our_node_id(); - nodes[1].splice_out(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[2].our_node_id(); + harness.nodes[1].splice_out(&cp_node_id, &harness.chan_b_id()); }, 0xa7 => { if !cfg!(splicing) { - assert_test_invariants(&nodes); + assert_test_invariants(&harness.nodes); return; } - let cp_node_id = nodes[1].our_node_id(); - nodes[2].splice_out(&cp_node_id, &chan_b_id); + let cp_node_id = harness.nodes[1].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 => nodes[0].sync_with_chain_state(&chain_state, Some(1)), - 0xa9 => nodes[1].sync_with_chain_state(&chain_state, Some(1)), - 0xaa => nodes[2].sync_with_chain_state(&chain_state, Some(1)), + 0xa8 => harness.nodes[0].sync_with_chain_state(&harness.chain_state, Some(1)), + 0xa9 => harness.nodes[1].sync_with_chain_state(&harness.chain_state, Some(1)), + 0xaa => 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 => nodes[0].sync_with_chain_state(&chain_state, None), - 0xac => nodes[1].sync_with_chain_state(&chain_state, None), - 0xad => nodes[2].sync_with_chain_state(&chain_state, None), + 0xab => harness.nodes[0].sync_with_chain_state(&harness.chain_state, None), + 0xac => harness.nodes[1].sync_with_chain_state(&harness.chain_state, None), + 0xad => 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, &mut nodes, &mut queues); - nodes[0].reload(v, &out, &router, chan_type); + harness.ab_link.disconnect_for_reload(0, &mut harness.nodes, &mut harness.queues); + harness.nodes[0].reload(v, &harness.out, &router, harness.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. - ab_link.disconnect_for_reload(1, &mut nodes, &mut queues); - bc_link.disconnect_for_reload(1, &mut nodes, &mut queues); - nodes[1].reload(v, &out, &router, chan_type); + harness.ab_link.disconnect_for_reload(1, &mut harness.nodes, &mut harness.queues); + harness.bc_link.disconnect_for_reload(1, &mut harness.nodes, &mut harness.queues); + harness.nodes[1].reload(v, &harness.out, &router, harness.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. - bc_link.disconnect_for_reload(2, &mut nodes, &mut queues); - nodes[2].reload(v, &out, &router, chan_type); + harness.bc_link.disconnect_for_reload(2, &mut harness.nodes, &mut harness.queues); + harness.nodes[2].reload(v, &harness.out, &router, harness.chan_type); }, - 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].node.signer_unblocked(None); + harness.nodes[0].node.signer_unblocked(None); }, 0xc4 => { - nodes[1] + harness.nodes[1] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[0].our_node_id(), chan_a_id)); - nodes[1].node.signer_unblocked(filter); + let filter = Some((harness.nodes[0].our_node_id(), harness.chan_a_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xc5 => { - nodes[1] + harness.nodes[1] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[2].our_node_id(), chan_b_id)); - nodes[1].node.signer_unblocked(filter); + let filter = Some((harness.nodes[2].our_node_id(), harness.chan_b_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xc6 => { - nodes[2] + harness.nodes[2] .keys_manager .enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - nodes[2].node.signer_unblocked(None); + harness.nodes[2].node.signer_unblocked(None); }, 0xc7 => { - nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[0].node.signer_unblocked(None); + harness.nodes[0] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + harness.nodes[0].node.signer_unblocked(None); }, 0xc8 => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[0].our_node_id(), chan_a_id)); - nodes[1].node.signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + let filter = Some((harness.nodes[0].our_node_id(), harness.chan_a_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xc9 => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[2].our_node_id(), chan_b_id)); - nodes[1].node.signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + let filter = Some((harness.nodes[2].our_node_id(), harness.chan_b_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xca => { - nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - nodes[2].node.signer_unblocked(None); + harness.nodes[2] + .keys_manager + .enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); + harness.nodes[2].node.signer_unblocked(None); }, 0xcb => { - nodes[0].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[0].node.signer_unblocked(None); + harness.nodes[0] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + harness.nodes[0].node.signer_unblocked(None); }, 0xcc => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[0].our_node_id(), chan_a_id)); - nodes[1].node.signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + let filter = Some((harness.nodes[0].our_node_id(), harness.chan_a_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xcd => { - nodes[1].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[2].our_node_id(), chan_b_id)); - nodes[1].node.signer_unblocked(filter); + harness.nodes[1] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + let filter = Some((harness.nodes[2].our_node_id(), harness.chan_b_id())); + harness.nodes[1].node.signer_unblocked(filter); }, 0xce => { - nodes[2].keys_manager.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - nodes[2].node.signer_unblocked(None); + harness.nodes[2] + .keys_manager + .enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + harness.nodes[2].node.signer_unblocked(None); }, 0xf0 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::First); + harness.ab_link.complete_monitor_updates_for_node( + 0, + &harness.nodes, + MonitorUpdateSelector::First, + ); }, 0xf1 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Second); + harness.ab_link.complete_monitor_updates_for_node( + 0, + &harness.nodes, + MonitorUpdateSelector::Second, + ); }, 0xf2 => { - ab_link.complete_monitor_updates_for_node(0, &nodes, MonitorUpdateSelector::Last); + 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); + harness.ab_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::First, + ); }, 0xf5 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second); + harness.ab_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Second, + ); }, 0xf6 => { - ab_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last); + 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); + harness.bc_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::First, + ); }, 0xf9 => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Second); + harness.bc_link.complete_monitor_updates_for_node( + 1, + &harness.nodes, + MonitorUpdateSelector::Second, + ); }, 0xfa => { - bc_link.complete_monitor_updates_for_node(1, &nodes, MonitorUpdateSelector::Last); + 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); + harness.bc_link.complete_monitor_updates_for_node( + 2, + &harness.nodes, + MonitorUpdateSelector::First, + ); }, 0xfd => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Second); + harness.bc_link.complete_monitor_updates_for_node( + 2, + &harness.nodes, + MonitorUpdateSelector::Second, + ); }, 0xfe => { - bc_link.complete_monitor_updates_for_node(2, &nodes, MonitorUpdateSelector::Last); + 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. - ab_link.reconnect(&mut nodes); - bc_link.reconnect(&mut nodes); + harness.ab_link.reconnect(&mut harness.nodes); + harness.bc_link.reconnect(&mut harness.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); + harness.nodes[0].keys_manager.enable_op_for_all_signers(op); + harness.nodes[1].keys_manager.enable_op_for_all_signers(op); + harness.nodes[2].keys_manager.enable_op_for_all_signers(op); } - nodes[0].node.signer_unblocked(None); - nodes[1].node.signer_unblocked(None); - nodes[2].node.signer_unblocked(None); + harness.nodes[0].node.signer_unblocked(None); + harness.nodes[1].node.signer_unblocked(None); + harness.nodes[2].node.signer_unblocked(None); process_all_events_impl( - &nodes, - &out, - &ab_link, - &bc_link, - &mut chain_state, - &mut payments, - &mut queues, + &harness.nodes, + &harness.out, + &harness.ab_link, + &harness.bc_link, + &mut harness.chain_state, + &mut harness.payments, + &mut harness.queues, ); // 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 { + for node in &harness.nodes { node.node.timer_tick_occurred(); } process_all_events_impl( - &nodes, - &out, - &ab_link, - &bc_link, - &mut chain_state, - &mut payments, - &mut queues, + &harness.nodes, + &harness.out, + &harness.ab_link, + &harness.bc_link, + &mut harness.chain_state, + &mut harness.payments, + &mut harness.queues, ); - payments.assert_all_resolved(); - payments.assert_claims_reported(); + harness.payments.assert_all_resolved(); + harness.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() { + for &chan_id in harness.ab_link.channel_ids() { assert!(send!(0, 1, chan_id, 10_000_000) || send!(1, 0, chan_id, 10_000_000)); } - for &chan_id in bc_link.channel_ids() { + for &chan_id in harness.bc_link.channel_ids() { assert!(send!(1, 2, chan_id, 10_000_000) || send!(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.nodes[0].record_last_htlc_clear_fee(); + harness.nodes[1].record_last_htlc_clear_fee(); + harness.nodes[2].record_last_htlc_clear_fee(); }, _ => test_return!(), } - for node in &mut nodes { - node.refresh_serialized_manager(); - } + harness.refresh_serialized_managers(); } } From 54a343418379608b51755f606090ef2d5b36c03b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 21 Apr 2026 16:00:30 +0200 Subject: [PATCH 19/19] Wrap chanmon consistency flow in Harness --- fuzz/src/chanmon_consistency.rs | 676 ++++++++++++++++++++------------ 1 file changed, 416 insertions(+), 260 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 25700201588..94632212f17 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -2085,6 +2085,153 @@ impl<'a, 'd, Out: Output + MaybeSend + MaybeSync> Harness<'a, 'd, Out> { assert_test_invariants(&self.nodes); } + fn send_direct( + &mut self, source_idx: usize, dest_idx: usize, dest_chan_id: ChannelId, amt: u64, + ) -> bool { + self.payments.send_direct(&self.nodes, source_idx, dest_idx, dest_chan_id, amt) + } + + fn send_hop( + &mut self, source_idx: usize, middle_idx: usize, middle_chan_id: ChannelId, + dest_idx: usize, dest_chan_id: ChannelId, amt: u64, + ) { + self.payments.send_hop( + &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, dest_chan_ids: &[ChannelId], amt: u64, + ) { + self.payments.send_mpp_direct(&self.nodes, source_idx, dest_idx, dest_chan_ids, amt); + } + + fn send_mpp_hop( + &mut self, source_idx: usize, middle_idx: usize, middle_chan_ids: &[ChannelId], + dest_idx: usize, dest_chan_ids: &[ChannelId], amt: u64, + ) { + self.payments.send_mpp_hop( + &self.nodes, + source_idx, + middle_idx, + middle_chan_ids, + dest_idx, + dest_chan_ids, + amt, + ); + } + + fn process_msg_events( + &mut self, node_idx: usize, corrupt_forward: bool, limit_events: ProcessMessages, + ) -> bool { + process_msg_events_impl( + node_idx, + corrupt_forward, + limit_events, + &self.nodes, + &self.out, + &mut self.queues, + ) + } + + fn process_events(&mut self, node_idx: usize, fail: bool) -> bool { + process_events_impl(node_idx, fail, &self.nodes, &mut self.chain_state, &mut self.payments) + } + + fn process_all_events(&mut self) { + process_all_events_impl( + &self.nodes, + &self.out, + &self.ab_link, + &self.bc_link, + &mut self.chain_state, + &mut self.payments, + &mut self.queues, + ); + } + + fn disconnect_ab(&mut self) { + self.ab_link.disconnect(&mut self.nodes, &mut self.queues); + } + + fn disconnect_bc(&mut self) { + self.bc_link.disconnect(&mut self.nodes, &mut self.queues); + } + + fn reconnect_ab(&mut self) { + self.ab_link.reconnect(&mut self.nodes); + } + + fn reconnect_bc(&mut self) { + self.bc_link.reconnect(&mut 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, &mut self.nodes, &mut self.queues); + }, + 1 => { + self.ab_link.disconnect_for_reload(1, &mut self.nodes, &mut self.queues); + self.bc_link.disconnect_for_reload(1, &mut self.nodes, &mut self.queues); + }, + 2 => { + self.bc_link.disconnect_for_reload(2, &mut 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) { + 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].node.signer_unblocked(None); + self.nodes[1].node.signer_unblocked(None); + self.nodes[2].node.signer_unblocked(None); + + self.process_all_events(); + + for node in self.nodes.iter() { + node.node.timer_tick_occurred(); + } + self.process_all_events(); + + self.payments.assert_all_resolved(); + self.payments.assert_claims_reported(); + + 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_direct(0, 1, chan_id, 10_000_000) + || self.send_direct(1, 0, chan_id, 10_000_000) + ); + } + for chan_id in chan_bc_ids { + assert!( + self.send_direct(1, 2, chan_id, 10_000_000) + || self.send_direct(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(); @@ -2452,113 +2599,14 @@ fn process_all_events_impl( pub fn do_test(data: &[u8], out: Out) { let router = FuzzRouter {}; let mut harness = Harness::new(data, out, &router); - let chan_a_id = harness.chan_a_id(); - let chan_b_id = harness.chan_b_id(); - macro_rules! test_return { - () => {{ + loop { + let v = if let Some(value) = harness.next_input_byte() { + value + } else { harness.finish(); return; - }}; - } - - loop { - macro_rules! process_msg_events { - ($node: expr, $corrupt_forward: expr, $limit_events: expr) => {{ - process_msg_events_impl( - $node, - $corrupt_forward, - $limit_events, - &harness.nodes, - &harness.out, - &mut harness.queues, - ) - }}; - } - - macro_rules! process_msg_noret { - ($node: expr, $corrupt_forward: expr, $limit_events: expr) => {{ - process_msg_events!($node, $corrupt_forward, $limit_events); - }}; - } - - macro_rules! process_events { - ($node: expr, $fail: expr) => {{ - process_events_impl( - $node, - $fail, - &harness.nodes, - &mut harness.chain_state, - &mut harness.payments, - ) - }}; - } - - macro_rules! process_ev_noret { - ($node: expr, $fail: expr) => {{ - process_events!($node, $fail); - }}; - } - - macro_rules! send { - ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ - harness.payments.send_direct( - &harness.nodes, - $source_idx, - $dest_idx, - $dest_chan_id, - $amt, - ) - }}; - } - - macro_rules! send_noret { - ($source_idx: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ - send!($source_idx, $dest_idx, $dest_chan_id, $amt); - }}; - } - - macro_rules! send_hop_noret { - ($source_idx: expr, $middle_idx: expr, $middle_chan_id: expr, $dest_idx: expr, $dest_chan_id: expr, $amt: expr) => {{ - harness.payments.send_hop( - &harness.nodes, - $source_idx, - $middle_idx, - $middle_chan_id, - $dest_idx, - $dest_chan_id, - $amt, - ); - }}; - } - - macro_rules! send_mpp_direct { - ($source_idx: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ - harness.payments.send_mpp_direct( - &harness.nodes, - $source_idx, - $dest_idx, - $dest_chan_ids, - $amt, - ); - }}; - } - - macro_rules! send_mpp_hop { - ($source_idx: expr, $middle_idx: expr, $middle_chan_ids: expr, $dest_idx: expr, $dest_chan_ids: expr, $amt: expr) => {{ - harness.payments.send_mpp_hop( - &harness.nodes, - $source_idx, - $middle_idx, - $middle_chan_ids, - $dest_idx, - $dest_chan_ids, - $amt, - ); - }}; - } - - let v = if let Some(value) = harness.next_input_byte() { value } else { test_return!() }; + }; harness .out .locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); @@ -2594,118 +2642,279 @@ pub fn do_test(data: &[u8], out: Out) { } }, - 0x0c => harness.ab_link.disconnect(&mut harness.nodes, &mut harness.queues), - 0x0d => harness.bc_link.disconnect(&mut harness.nodes, &mut harness.queues), - 0x0e => harness.ab_link.reconnect(&mut harness.nodes), - 0x0f => harness.bc_link.reconnect(&mut harness.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_events(0, true, ProcessMessages::AllMessages); + }, + 0x11 => { + harness.process_msg_events(0, false, ProcessMessages::AllMessages); + }, + 0x12 => { + harness.process_msg_events(0, true, ProcessMessages::OneMessage); + }, + 0x13 => { + harness.process_msg_events(0, false, ProcessMessages::OneMessage); + }, + 0x14 => { + harness.process_msg_events(0, true, ProcessMessages::OnePendingMessage); + }, + 0x15 => { + harness.process_msg_events(0, false, ProcessMessages::OnePendingMessage); + }, - 0x16 => process_ev_noret!(0, true), - 0x17 => process_ev_noret!(0, false), + 0x16 => { + harness.process_events(0, true); + }, + 0x17 => { + harness.process_events(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_events(1, true, ProcessMessages::AllMessages); + }, + 0x19 => { + harness.process_msg_events(1, false, ProcessMessages::AllMessages); + }, + 0x1a => { + harness.process_msg_events(1, true, ProcessMessages::OneMessage); + }, + 0x1b => { + harness.process_msg_events(1, false, ProcessMessages::OneMessage); + }, + 0x1c => { + harness.process_msg_events(1, true, ProcessMessages::OnePendingMessage); + }, + 0x1d => { + harness.process_msg_events(1, false, ProcessMessages::OnePendingMessage); + }, - 0x1e => process_ev_noret!(1, true), - 0x1f => process_ev_noret!(1, false), + 0x1e => { + harness.process_events(1, true); + }, + 0x1f => { + harness.process_events(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_events(2, true, ProcessMessages::AllMessages); + }, + 0x21 => { + harness.process_msg_events(2, false, ProcessMessages::AllMessages); + }, + 0x22 => { + harness.process_msg_events(2, true, ProcessMessages::OneMessage); + }, + 0x23 => { + harness.process_msg_events(2, false, ProcessMessages::OneMessage); + }, + 0x24 => { + harness.process_msg_events(2, true, ProcessMessages::OnePendingMessage); + }, + 0x25 => { + harness.process_msg_events(2, false, ProcessMessages::OnePendingMessage); + }, - 0x26 => process_ev_noret!(2, true), - 0x27 => process_ev_noret!(2, false), + 0x26 => { + harness.process_events(2, true); + }, + 0x27 => { + harness.process_events(2, false); + }, // 1/10th the channel size: - 0x30 => send_noret!(0, 1, chan_a_id, 10_000_000), - 0x31 => send_noret!(1, 0, chan_a_id, 10_000_000), - 0x32 => send_noret!(1, 2, chan_b_id, 10_000_000), - 0x33 => send_noret!(2, 1, chan_b_id, 10_000_000), - 0x34 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10_000_000), - 0x35 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10_000_000), - - 0x38 => send_noret!(0, 1, chan_a_id, 1_000_000), - 0x39 => send_noret!(1, 0, chan_a_id, 1_000_000), - 0x3a => send_noret!(1, 2, chan_b_id, 1_000_000), - 0x3b => send_noret!(2, 1, chan_b_id, 1_000_000), - 0x3c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1_000_000), - 0x3d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 1_000_000), - - 0x40 => send_noret!(0, 1, chan_a_id, 100_000), - 0x41 => send_noret!(1, 0, chan_a_id, 100_000), - 0x42 => send_noret!(1, 2, chan_b_id, 100_000), - 0x43 => send_noret!(2, 1, chan_b_id, 100_000), - 0x44 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 100_000), - 0x45 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 100_000), - - 0x48 => send_noret!(0, 1, chan_a_id, 10_000), - 0x49 => send_noret!(1, 0, chan_a_id, 10_000), - 0x4a => send_noret!(1, 2, chan_b_id, 10_000), - 0x4b => send_noret!(2, 1, chan_b_id, 10_000), - 0x4c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10_000), - 0x4d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10_000), - - 0x50 => send_noret!(0, 1, chan_a_id, 1_000), - 0x51 => send_noret!(1, 0, chan_a_id, 1_000), - 0x52 => send_noret!(1, 2, chan_b_id, 1_000), - 0x53 => send_noret!(2, 1, chan_b_id, 1_000), - 0x54 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1_000), - 0x55 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 1_000), - - 0x58 => send_noret!(0, 1, chan_a_id, 100), - 0x59 => send_noret!(1, 0, chan_a_id, 100), - 0x5a => send_noret!(1, 2, chan_b_id, 100), - 0x5b => send_noret!(2, 1, chan_b_id, 100), - 0x5c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 100), - 0x5d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 100), - - 0x60 => send_noret!(0, 1, chan_a_id, 10), - 0x61 => send_noret!(1, 0, chan_a_id, 10), - 0x62 => send_noret!(1, 2, chan_b_id, 10), - 0x63 => send_noret!(2, 1, chan_b_id, 10), - 0x64 => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 10), - 0x65 => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 10), - - 0x68 => send_noret!(0, 1, chan_a_id, 1), - 0x69 => send_noret!(1, 0, chan_a_id, 1), - 0x6a => send_noret!(1, 2, chan_b_id, 1), - 0x6b => send_noret!(2, 1, chan_b_id, 1), - 0x6c => send_hop_noret!(0, 1, chan_a_id, 2, chan_b_id, 1), - 0x6d => send_hop_noret!(2, 1, chan_b_id, 0, chan_a_id, 1), + 0x30 => { + harness.send_direct(0, 1, harness.chan_a_id(), 10_000_000); + }, + 0x31 => { + harness.send_direct(1, 0, harness.chan_a_id(), 10_000_000); + }, + 0x32 => { + harness.send_direct(1, 2, harness.chan_b_id(), 10_000_000); + }, + 0x33 => { + harness.send_direct(2, 1, harness.chan_b_id(), 10_000_000); + }, + 0x34 => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 10_000_000); + }, + 0x35 => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 10_000_000); + }, + + 0x38 => { + harness.send_direct(0, 1, harness.chan_a_id(), 1_000_000); + }, + 0x39 => { + harness.send_direct(1, 0, harness.chan_a_id(), 1_000_000); + }, + 0x3a => { + harness.send_direct(1, 2, harness.chan_b_id(), 1_000_000); + }, + 0x3b => { + harness.send_direct(2, 1, harness.chan_b_id(), 1_000_000); + }, + 0x3c => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 1_000_000); + }, + 0x3d => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 1_000_000); + }, + + 0x40 => { + harness.send_direct(0, 1, harness.chan_a_id(), 100_000); + }, + 0x41 => { + harness.send_direct(1, 0, harness.chan_a_id(), 100_000); + }, + 0x42 => { + harness.send_direct(1, 2, harness.chan_b_id(), 100_000); + }, + 0x43 => { + harness.send_direct(2, 1, harness.chan_b_id(), 100_000); + }, + 0x44 => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 100_000); + }, + 0x45 => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 100_000); + }, + + 0x48 => { + harness.send_direct(0, 1, harness.chan_a_id(), 10_000); + }, + 0x49 => { + harness.send_direct(1, 0, harness.chan_a_id(), 10_000); + }, + 0x4a => { + harness.send_direct(1, 2, harness.chan_b_id(), 10_000); + }, + 0x4b => { + harness.send_direct(2, 1, harness.chan_b_id(), 10_000); + }, + 0x4c => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 10_000); + }, + 0x4d => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 10_000); + }, + + 0x50 => { + harness.send_direct(0, 1, harness.chan_a_id(), 1_000); + }, + 0x51 => { + harness.send_direct(1, 0, harness.chan_a_id(), 1_000); + }, + 0x52 => { + harness.send_direct(1, 2, harness.chan_b_id(), 1_000); + }, + 0x53 => { + harness.send_direct(2, 1, harness.chan_b_id(), 1_000); + }, + 0x54 => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 1_000); + }, + 0x55 => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 1_000); + }, + + 0x58 => { + harness.send_direct(0, 1, harness.chan_a_id(), 100); + }, + 0x59 => { + harness.send_direct(1, 0, harness.chan_a_id(), 100); + }, + 0x5a => { + harness.send_direct(1, 2, harness.chan_b_id(), 100); + }, + 0x5b => { + harness.send_direct(2, 1, harness.chan_b_id(), 100); + }, + 0x5c => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 100); + }, + 0x5d => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 100); + }, + + 0x60 => { + harness.send_direct(0, 1, harness.chan_a_id(), 10); + }, + 0x61 => { + harness.send_direct(1, 0, harness.chan_a_id(), 10); + }, + 0x62 => { + harness.send_direct(1, 2, harness.chan_b_id(), 10); + }, + 0x63 => { + harness.send_direct(2, 1, harness.chan_b_id(), 10); + }, + 0x64 => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 10); + }, + 0x65 => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 10); + }, + + 0x68 => { + harness.send_direct(0, 1, harness.chan_a_id(), 1); + }, + 0x69 => { + harness.send_direct(1, 0, harness.chan_a_id(), 1); + }, + 0x6a => { + harness.send_direct(1, 2, harness.chan_b_id(), 1); + }, + 0x6b => { + harness.send_direct(2, 1, harness.chan_b_id(), 1); + }, + 0x6c => { + harness.send_hop(0, 1, harness.chan_a_id(), 2, harness.chan_b_id(), 1); + }, + 0x6d => { + harness.send_hop(2, 1, harness.chan_b_id(), 0, harness.chan_a_id(), 1); + }, // MPP payments // 0x70: direct MPP from 0 to 1 (multi A-B channels) - 0x70 => send_mpp_direct!(0, 1, harness.ab_link.channel_ids(), 1_000_000), + 0x70 => { + let chan_ab_ids = harness.ab_link.channel_ids().clone(); + harness.send_mpp_direct(0, 1, &chan_ab_ids, 1_000_000); + }, // 0x71: MPP 0->1->2, multi channels on first hop (A-B) - 0x71 => send_mpp_hop!(0, 1, harness.ab_link.channel_ids(), 2, &[chan_b_id], 1_000_000), + 0x71 => { + let chan_ab_ids = harness.ab_link.channel_ids().clone(); + let chan_b_id = harness.chan_b_id(); + harness.send_mpp_hop(0, 1, &chan_ab_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, - harness.ab_link.channel_ids(), - 2, - harness.bc_link.channel_ids(), - 1_000_000 - ) + let chan_ab_ids = harness.ab_link.channel_ids().clone(); + let chan_bc_ids = harness.bc_link.channel_ids().clone(); + harness.send_mpp_hop(0, 1, &chan_ab_ids, 2, &chan_bc_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, harness.bc_link.channel_ids(), 1_000_000), + 0x73 => { + let chan_a_id = harness.chan_a_id(); + let chan_bc_ids = harness.bc_link.channel_ids().clone(); + harness.send_mpp_hop(0, 1, &[chan_a_id], 2, &chan_bc_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), + 0x74 => { + let chan_a_id = harness.chan_a_id(); + harness.send_mpp_direct(0, 1, &[chan_a_id, chan_a_id, chan_a_id], 1_000_000); + }, 0x80 => harness.nodes[0].bump_fee_estimate(harness.chan_type), 0x81 => harness.nodes[0].reset_fee_estimate(), @@ -2792,21 +3001,17 @@ 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. - harness.ab_link.disconnect_for_reload(0, &mut harness.nodes, &mut harness.queues); - harness.nodes[0].reload(v, &harness.out, &router, harness.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. - harness.ab_link.disconnect_for_reload(1, &mut harness.nodes, &mut harness.queues); - harness.bc_link.disconnect_for_reload(1, &mut harness.nodes, &mut harness.queues); - harness.nodes[1].reload(v, &harness.out, &router, harness.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. - harness.bc_link.disconnect_for_reload(2, &mut harness.nodes, &mut harness.queues); - harness.nodes[2].reload(v, &harness.out, &router, harness.chan_type); + harness.restart_node(2, v, &router); }, 0xc0 => harness.nodes[0].keys_manager.disable_supported_ops_for_all_signers(), @@ -2982,61 +3187,12 @@ pub fn do_test(data: &[u8], out: Out) { 0xff => { // Test that no channel is in a stuck state where neither party can send funds even // after we resolve all pending events. - - harness.ab_link.reconnect(&mut harness.nodes); - harness.bc_link.reconnect(&mut harness.nodes); - - for op in SUPPORTED_SIGNER_OPS { - harness.nodes[0].keys_manager.enable_op_for_all_signers(op); - harness.nodes[1].keys_manager.enable_op_for_all_signers(op); - harness.nodes[2].keys_manager.enable_op_for_all_signers(op); - } - harness.nodes[0].node.signer_unblocked(None); - harness.nodes[1].node.signer_unblocked(None); - harness.nodes[2].node.signer_unblocked(None); - - process_all_events_impl( - &harness.nodes, - &harness.out, - &harness.ab_link, - &harness.bc_link, - &mut harness.chain_state, - &mut harness.payments, - &mut harness.queues, - ); - - // 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 &harness.nodes { - node.node.timer_tick_occurred(); - } - process_all_events_impl( - &harness.nodes, - &harness.out, - &harness.ab_link, - &harness.bc_link, - &mut harness.chain_state, - &mut harness.payments, - &mut harness.queues, - ); - - harness.payments.assert_all_resolved(); - harness.payments.assert_claims_reported(); - - // Finally, make sure that at least one end of each channel can make a substantial payment - for &chan_id in harness.ab_link.channel_ids() { - assert!(send!(0, 1, chan_id, 10_000_000) || send!(1, 0, chan_id, 10_000_000)); - } - for &chan_id in harness.bc_link.channel_ids() { - assert!(send!(1, 2, chan_id, 10_000_000) || send!(2, 1, chan_id, 10_000_000)); - } - - harness.nodes[0].record_last_htlc_clear_fee(); - harness.nodes[1].record_last_htlc_clear_fee(); - harness.nodes[2].record_last_htlc_clear_fee(); + harness.settle_all(); + }, + _ => { + assert_test_invariants(&harness.nodes); + return; }, - _ => test_return!(), } harness.refresh_serialized_managers();