Merge pull request #3054 from TheBlueMatt/2024-04-fuzz-bolt11
[rust-lightning] / lightning-persister / src / fs_store.rs
index f74806d7d8e1ce4e19ec9cd2bcc06463164dcab9..8a144f6196b814104665572366f17c2b7ba02d72 100644 (file)
@@ -26,7 +26,7 @@ macro_rules! call {
 }
 
 #[cfg(target_os = "windows")]
-fn path_to_windows_str<T: AsRef<OsStr>>(path: T) -> Vec<u16> {
+fn path_to_windows_str<T: AsRef<OsStr>>(path: &T) -> Vec<u16> {
        path.as_ref().encode_wide().chain(Some(0)).collect()
 }
 
@@ -67,7 +67,7 @@ impl FilesystemStore {
                }
        }
 
-       fn get_dest_dir_path(&self, namespace: &str, sub_namespace: &str) -> std::io::Result<PathBuf> {
+       fn get_dest_dir_path(&self, primary_namespace: &str, secondary_namespace: &str) -> std::io::Result<PathBuf> {
                let mut dest_dir_path = {
                        #[cfg(target_os = "windows")]
                        {
@@ -81,9 +81,9 @@ impl FilesystemStore {
                        }
                };
 
-               dest_dir_path.push(namespace);
-               if !sub_namespace.is_empty() {
-                       dest_dir_path.push(sub_namespace);
+               dest_dir_path.push(primary_namespace);
+               if !secondary_namespace.is_empty() {
+                       dest_dir_path.push(secondary_namespace);
                }
 
                Ok(dest_dir_path)
@@ -91,10 +91,10 @@ impl FilesystemStore {
 }
 
 impl KVStore for FilesystemStore {
-       fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> std::io::Result<Vec<u8>> {
-               check_namespace_key_validity(namespace, sub_namespace, Some(key), "read")?;
+       fn read(&self, primary_namespace: &str, secondary_namespace: &str, key: &str) -> std::io::Result<Vec<u8>> {
+               check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "read")?;
 
-               let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+               let mut dest_file_path = self.get_dest_dir_path(primary_namespace, secondary_namespace)?;
                dest_file_path.push(key);
 
                let mut buf = Vec::new();
@@ -114,10 +114,10 @@ impl KVStore for FilesystemStore {
                Ok(buf)
        }
 
-       fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> std::io::Result<()> {
-               check_namespace_key_validity(namespace, sub_namespace, Some(key), "write")?;
+       fn write(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8]) -> std::io::Result<()> {
+               check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?;
 
-               let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+               let mut dest_file_path = self.get_dest_dir_path(primary_namespace, secondary_namespace)?;
                dest_file_path.push(key);
 
                let parent_directory = dest_file_path
@@ -164,8 +164,8 @@ impl KVStore for FilesystemStore {
                                let res = if dest_file_path.exists() {
                                        call!(unsafe {
                                                windows_sys::Win32::Storage::FileSystem::ReplaceFileW(
-                                                       path_to_windows_str(dest_file_path.clone()).as_ptr(),
-                                                       path_to_windows_str(tmp_file_path).as_ptr(),
+                                                       path_to_windows_str(&dest_file_path).as_ptr(),
+                                                       path_to_windows_str(&tmp_file_path).as_ptr(),
                                                        std::ptr::null(),
                                                        windows_sys::Win32::Storage::FileSystem::REPLACEFILE_IGNORE_MERGE_ERRORS,
                                                        std::ptr::null_mut() as *const core::ffi::c_void,
@@ -175,8 +175,8 @@ impl KVStore for FilesystemStore {
                                } else {
                                        call!(unsafe {
                                                windows_sys::Win32::Storage::FileSystem::MoveFileExW(
-                                                       path_to_windows_str(tmp_file_path).as_ptr(),
-                                                       path_to_windows_str(dest_file_path.clone()).as_ptr(),
+                                                       path_to_windows_str(&tmp_file_path).as_ptr(),
+                                                       path_to_windows_str(&dest_file_path).as_ptr(),
                                                        windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
                                                        | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
                                                        )
@@ -201,10 +201,10 @@ impl KVStore for FilesystemStore {
                res
        }
 
-       fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, lazy: bool) -> std::io::Result<()> {
-               check_namespace_key_validity(namespace, sub_namespace, Some(key), "remove")?;
+       fn remove(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool) -> std::io::Result<()> {
+               check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "remove")?;
 
-               let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+               let mut dest_file_path = self.get_dest_dir_path(primary_namespace, secondary_namespace)?;
                dest_file_path.push(key);
 
                if !dest_file_path.is_file() {
@@ -263,8 +263,8 @@ impl KVStore for FilesystemStore {
 
                                        call!(unsafe {
                                                windows_sys::Win32::Storage::FileSystem::MoveFileExW(
-                                                       path_to_windows_str(dest_file_path).as_ptr(),
-                                                       path_to_windows_str(trash_file_path.clone()).as_ptr(),
+                                                       path_to_windows_str(&dest_file_path).as_ptr(),
+                                                       path_to_windows_str(&trash_file_path).as_ptr(),
                                                        windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
                                                        | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
                                                        )
@@ -290,10 +290,10 @@ impl KVStore for FilesystemStore {
                Ok(())
        }
 
-       fn list(&self, namespace: &str, sub_namespace: &str) -> std::io::Result<Vec<String>> {
-               check_namespace_key_validity(namespace, sub_namespace, None, "list")?;
+       fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> std::io::Result<Vec<String>> {
+               check_namespace_key_validity(primary_namespace, secondary_namespace, None, "list")?;
 
-               let prefixed_dest = self.get_dest_dir_path(namespace, sub_namespace)?;
+               let prefixed_dest = self.get_dest_dir_path(primary_namespace, secondary_namespace)?;
                let mut keys = Vec::new();
 
                if !Path::new(&prefixed_dest).exists() {
@@ -320,7 +320,7 @@ impl KVStore for FilesystemStore {
 
                        let metadata = p.metadata()?;
 
-                       // We allow the presence of directories in the empty namespace and just skip them.
+                       // We allow the presence of directories in the empty primary namespace and just skip them.
                        if metadata.is_dir() {
                                continue;
                        }
@@ -328,9 +328,9 @@ impl KVStore for FilesystemStore {
                        // If we otherwise don't find a file at the given path something went wrong.
                        if !metadata.is_file() {
                                debug_assert!(false, "Failed to list keys of {}/{}: file couldn't be accessed.",
-                                       PrintableString(namespace), PrintableString(sub_namespace));
+                                       PrintableString(primary_namespace), PrintableString(secondary_namespace));
                                let msg = format!("Failed to list keys of {}/{}: file couldn't be accessed.",
-                                       PrintableString(namespace), PrintableString(sub_namespace));
+                                       PrintableString(primary_namespace), PrintableString(secondary_namespace));
                                return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
                        }
 
@@ -342,17 +342,17 @@ impl KVStore for FilesystemStore {
                                                }
                                        } else {
                                                debug_assert!(false, "Failed to list keys of {}/{}: file path is not valid UTF-8",
-                                                       PrintableString(namespace), PrintableString(sub_namespace));
+                                                       PrintableString(primary_namespace), PrintableString(secondary_namespace));
                                                let msg = format!("Failed to list keys of {}/{}: file path is not valid UTF-8",
-                                                       PrintableString(namespace), PrintableString(sub_namespace));
+                                                       PrintableString(primary_namespace), PrintableString(secondary_namespace));
                                                return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
                                        }
                                }
                                Err(e) => {
                                        debug_assert!(false, "Failed to list keys of {}/{}: {}",
-                                               PrintableString(namespace), PrintableString(sub_namespace), e);
+                                               PrintableString(primary_namespace), PrintableString(secondary_namespace), e);
                                        let msg = format!("Failed to list keys of {}/{}: {}",
-                                               PrintableString(namespace), PrintableString(sub_namespace), e);
+                                               PrintableString(primary_namespace), PrintableString(secondary_namespace), e);
                                        return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
                                }
                        }
@@ -367,7 +367,30 @@ 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::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::str::FromStr;
+
+       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() {
@@ -376,4 +399,125 @@ 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 an UnrecoverableError.
+       // 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();
+
+               // Set the store's directory to read-only, which should result in
+               // returning an unrecoverable 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_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
+                       index: 0
+               };
+               match store.persist_new_channel(test_txo, &added_monitors[0].1) {
+                       ChannelMonitorUpdateStatus::UnrecoverableError => {},
+                       _ => 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 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].1.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_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
+                       index: 0
+               };
+               match store.persist_new_channel(test_txo, &added_monitors[0].1) {
+                       ChannelMonitorUpdateStatus::UnrecoverableError => {},
+                       _ => panic!("unexpected result from persisting new channel")
+               }
+
+               nodes[1].node.get_and_clear_pending_msg_events();
+               added_monitors.clear();
+       }
+}
+
+#[cfg(ldk_bench)]
+/// Benches
+pub mod bench {
+       use criterion::Criterion;
+
+       /// Bench!
+       pub fn bench_sends(bench: &mut Criterion) {
+               let store_a = super::FilesystemStore::new("bench_filesystem_store_a".into());
+               let store_b = super::FilesystemStore::new("bench_filesystem_store_b".into());
+               lightning::ln::channelmanager::bench::bench_two_sends(
+                       bench, "bench_filesystem_persisted_sends", store_a, store_b);
+       }
 }