X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-persister%2Fsrc%2Ffs_store.rs;h=8a144f6196b814104665572366f17c2b7ba02d72;hb=806b7f0e312c59c87fd628fb71e7c4a77a39645a;hp=f74806d7d8e1ce4e19ec9cd2bcc06463164dcab9;hpb=f22d1b63902404a15ca808c977f8364cbf90d277;p=rust-lightning diff --git a/lightning-persister/src/fs_store.rs b/lightning-persister/src/fs_store.rs index f74806d7..8a144f61 100644 --- a/lightning-persister/src/fs_store.rs +++ b/lightning-persister/src/fs_store.rs @@ -26,7 +26,7 @@ macro_rules! call { } #[cfg(target_os = "windows")] -fn path_to_windows_str>(path: T) -> Vec { +fn path_to_windows_str>(path: &T) -> Vec { 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 { + fn get_dest_dir_path(&self, primary_namespace: &str, secondary_namespace: &str) -> std::io::Result { 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> { - check_namespace_key_validity(namespace, sub_namespace, Some(key), "read")?; + fn read(&self, primary_namespace: &str, secondary_namespace: &str, key: &str) -> std::io::Result> { + 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> { - check_namespace_key_validity(namespace, sub_namespace, None, "list")?; + fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> std::io::Result> { + 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); + } }