From 69c96e306db45fddae31fa1e9237b02c9aa98864 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 4 Aug 2023 16:27:39 +0200 Subject: [PATCH] Migrate `FilesystemPersister` tests to `FilesystemStore` --- lightning-persister/Cargo.toml | 1 + lightning-persister/src/fs_store.rs | 140 +++++++++++++++++++++++++- lightning-persister/src/test_utils.rs | 71 ++++++++++++- 3 files changed, 210 insertions(+), 2 deletions(-) diff --git a/lightning-persister/Cargo.toml b/lightning-persister/Cargo.toml index 7ba422322..361ab0c57 100644 --- a/lightning-persister/Cargo.toml +++ b/lightning-persister/Cargo.toml @@ -25,3 +25,4 @@ criterion = { version = "0.4", optional = true, default-features = false } [dev-dependencies] lightning = { version = "0.0.116", path = "../lightning", features = ["_test_utils"] } +bitcoin = { version = "0.29.0", default-features = false } diff --git a/lightning-persister/src/fs_store.rs b/lightning-persister/src/fs_store.rs index 6cf54e2e9..692c296cf 100644 --- a/lightning-persister/src/fs_store.rs +++ b/lightning-persister/src/fs_store.rs @@ -274,7 +274,36 @@ impl KVStore for FilesystemStore { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::do_read_write_remove_list_persist; + use crate::test_utils::{do_read_write_remove_list_persist, do_test_store}; + + use bitcoin::hashes::hex::FromHex; + use bitcoin::Txid; + + use lightning::chain::ChannelMonitorUpdateStatus; + use lightning::chain::chainmonitor::Persist; + use lightning::chain::transaction::OutPoint; + use lightning::check_closed_event; + use lightning::events::{ClosureReason, MessageSendEventsProvider}; + use lightning::ln::functional_test_utils::*; + use lightning::util::test_utils; + use lightning::util::persist::read_channel_monitors; + use std::fs; + #[cfg(target_os = "windows")] + use { + lightning::get_event_msg, + lightning::ln::msgs::ChannelMessageHandler, + }; + + impl Drop for FilesystemStore{ + fn drop(&mut self) { + // We test for invalid directory names, so it's OK if directory removal + // fails. + match fs::remove_dir_all(&self.data_dir) { + Err(e) => println!("Failed to remove test persister directory: {}", e), + _ => {} + } + } + } #[test] fn read_write_remove_list_persist() { @@ -283,4 +312,113 @@ mod tests { let fs_store = FilesystemStore::new(temp_path); do_read_write_remove_list_persist(&fs_store); } + + #[test] + fn test_if_monitors_is_not_dir() { + let store = FilesystemStore::new("test_monitors_is_not_dir".into()); + + fs::create_dir_all(&store.get_data_dir()).unwrap(); + let mut path = std::path::PathBuf::from(&store.get_data_dir()); + path.push("monitors"); + fs::File::create(path).unwrap(); + + let chanmon_cfgs = create_chanmon_cfgs(1); + let mut node_cfgs = create_node_cfgs(1, &chanmon_cfgs); + let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &store, node_cfgs[0].keys_manager); + node_cfgs[0].chain_monitor = chain_mon_0; + let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[None]); + let nodes = create_network(1, &node_cfgs, &node_chanmgrs); + + // Check that read_channel_monitors() returns error if monitors/ is not a + // directory. + assert!(read_channel_monitors(&store, nodes[0].keys_manager, nodes[0].keys_manager).is_err()); + } + + #[test] + fn test_filesystem_store() { + // Create the nodes, giving them FilesystemStores for data stores. + let store_0 = FilesystemStore::new("test_filesystem_store_0".into()); + let store_1 = FilesystemStore::new("test_filesystem_store_1".into()); + do_test_store(&store_0, &store_1) + } + + // Test that if the store's path to channel data is read-only, writing a + // monitor to it results in the store returning a PermanentFailure. + // Windows ignores the read-only flag for folders, so this test is Unix-only. + #[cfg(not(target_os = "windows"))] + #[test] + fn test_readonly_dir_perm_failure() { + let store = FilesystemStore::new("test_readonly_dir_perm_failure".into()); + fs::create_dir_all(&store.get_data_dir()).unwrap(); + + // Set up a dummy channel and force close. This will produce a monitor + // that we can then use to test persistence. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let chan = create_announced_chan_between_nodes(&nodes, 0, 1); + nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap(); + check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000); + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap(); + let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap(); + + // Set the store's directory to read-only, which should result in + // returning a permanent failure when we then attempt to persist a + // channel update. + let path = &store.get_data_dir(); + let mut perms = fs::metadata(path).unwrap().permissions(); + perms.set_readonly(true); + fs::set_permissions(path, perms).unwrap(); + + let test_txo = OutPoint { + txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + index: 0 + }; + match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { + ChannelMonitorUpdateStatus::PermanentFailure => {}, + _ => panic!("unexpected result from persisting new channel") + } + + nodes[1].node.get_and_clear_pending_msg_events(); + added_monitors.clear(); + } + + // Test that if a store's directory name is invalid, monitor persistence + // will fail. + #[cfg(target_os = "windows")] + #[test] + fn test_fail_on_open() { + // Set up a dummy channel and force close. This will produce a monitor + // that we can then use to test persistence. + let chanmon_cfgs = create_chanmon_cfgs(2); + let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let chan = create_announced_chan_between_nodes(&nodes, 0, 1); + nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap(); + check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000); + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap(); + let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap(); + + // Create the store with an invalid directory name and test that the + // channel fails to open because the directories fail to be created. There + // don't seem to be invalid filename characters on Unix that Rust doesn't + // handle, hence why the test is Windows-only. + let store = FilesystemStore::new(":<>/".into()); + + let test_txo = OutPoint { + txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + index: 0 + }; + match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { + ChannelMonitorUpdateStatus::PermanentFailure => {}, + _ => panic!("unexpected result from persisting new channel") + } + + nodes[1].node.get_and_clear_pending_msg_events(); + added_monitors.clear(); + } } diff --git a/lightning-persister/src/test_utils.rs b/lightning-persister/src/test_utils.rs index f6cc73c81..df93f1646 100644 --- a/lightning-persister/src/test_utils.rs +++ b/lightning-persister/src/test_utils.rs @@ -1,4 +1,9 @@ -use lightning::util::persist::KVStore; +use lightning::util::persist::{KVStore, read_channel_monitors}; +use lightning::ln::functional_test_utils::*; +use lightning::chain::channelmonitor::CLOSED_CHANNEL_UPDATE_ID; +use lightning::util::test_utils; +use lightning::{check_closed_broadcast, check_closed_event, check_added_monitors}; +use lightning::events::ClosureReason; use std::io::Cursor; @@ -30,3 +35,67 @@ pub(crate) fn do_read_write_remove_list_persist(kv_store: &K) { let listed_keys = kv_store.list(namespace).unwrap(); assert_eq!(listed_keys.len(), 0); } + +// Integration-test the given KVStore implementation. Test relaying a few payments and check that +// the persisted data is updated the appropriate number of times. +pub(crate) fn do_test_store(store_0: &K, store_1: &K) { + let chanmon_cfgs = create_chanmon_cfgs(2); + let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, store_0, node_cfgs[0].keys_manager); + let chain_mon_1 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[1].chain_source), &chanmon_cfgs[1].tx_broadcaster, &chanmon_cfgs[1].logger, &chanmon_cfgs[1].fee_estimator, store_1, node_cfgs[1].keys_manager); + node_cfgs[0].chain_monitor = chain_mon_0; + node_cfgs[1].chain_monitor = chain_mon_1; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Check that the persisted channel data is empty before any channels are + // open. + let mut persisted_chan_data_0 = read_channel_monitors(store_0, nodes[0].keys_manager, nodes[0].keys_manager).unwrap(); + assert_eq!(persisted_chan_data_0.len(), 0); + let mut persisted_chan_data_1 = read_channel_monitors(store_1, nodes[1].keys_manager, nodes[1].keys_manager).unwrap(); + assert_eq!(persisted_chan_data_1.len(), 0); + + // Helper to make sure the channel is on the expected update ID. + macro_rules! check_persisted_data { + ($expected_update_id: expr) => { + persisted_chan_data_0 = read_channel_monitors(store_0, nodes[0].keys_manager, nodes[0].keys_manager).unwrap(); + assert_eq!(persisted_chan_data_0.len(), 1); + for (_, mon) in persisted_chan_data_0.iter() { + assert_eq!(mon.get_latest_update_id(), $expected_update_id); + } + persisted_chan_data_1 = read_channel_monitors(store_1, nodes[1].keys_manager, nodes[1].keys_manager).unwrap(); + assert_eq!(persisted_chan_data_1.len(), 1); + for (_, mon) in persisted_chan_data_1.iter() { + assert_eq!(mon.get_latest_update_id(), $expected_update_id); + } + } + } + + // Create some initial channel and check that a channel was persisted. + let _ = create_announced_chan_between_nodes(&nodes, 0, 1); + check_persisted_data!(0); + + // Send a few payments and make sure the monitors are updated to the latest. + send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000); + check_persisted_data!(5); + send_payment(&nodes[1], &vec!(&nodes[0])[..], 4000000); + check_persisted_data!(10); + + // Force close because cooperative close doesn't result in any persisted + // updates. + nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap(); + check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000); + check_closed_broadcast!(nodes[0], true); + check_added_monitors!(nodes[0], 1); + + let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap(); + assert_eq!(node_txn.len(), 1); + + connect_block(&nodes[1], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![node_txn[0].clone(), node_txn[0].clone()])); + check_closed_broadcast!(nodes[1], true); + check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000); + check_added_monitors!(nodes[1], 1); + + // Make sure everything is persisted as expected after close. + check_persisted_data!(CLOSED_CHANNEL_UPDATE_ID); +} -- 2.39.5