Skip to content

Commit 668b35b

Browse files
committed
Detect nested v1 filesystem data
FilesystemStoreV2 already rejected v1 data when a key file was found at the store root, but it did not inspect namespace directories. This missed v1 layouts such as primary/key, where v2 expects primary to contain secondary namespace directories. For example, an ldk-node store can contain a BDK descriptor below a namespace directory. The previous check would accept that directory as v2 data because the root contained only directories, leaving the incompatible descriptor file undetected.
1 parent 0c37f08 commit 668b35b

1 file changed

Lines changed: 66 additions & 9 deletions

File tree

  • lightning-persister/src/fs_store

lightning-persister/src/fs_store/v2.rs

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use std::sync::Arc;
2121
/// An error returned when constructing a [`FilesystemStoreV2`].
2222
#[derive(Debug)]
2323
pub enum FilesystemStoreV2Error {
24-
/// The data directory contains a file at the top level, indicating it was previously used
25-
/// by [`FilesystemStore`] (v1). Contains the path of the offending file.
24+
/// The data directory contains a file where v2 expects a namespace directory, indicating it
25+
/// was previously used by [`FilesystemStore`] (v1). Contains the path of the offending file.
2626
///
2727
/// [`FilesystemStore`]: crate::fs_store::v1::FilesystemStore
2828
V1DataDetected(PathBuf),
@@ -35,7 +35,7 @@ impl fmt::Display for FilesystemStoreV2Error {
3535
match self {
3636
Self::V1DataDetected(path) => write!(
3737
f,
38-
"Found file `{}` in the top-level data directory. \
38+
"Found file `{}` where FilesystemStoreV2 expects a namespace directory. \
3939
This indicates the directory was previously used by FilesystemStore (v1). \
4040
Please migrate your data or use a different directory.",
4141
path.display()
@@ -97,18 +97,28 @@ impl FilesystemStoreV2 {
9797
/// Constructs a new [`FilesystemStoreV2`].
9898
///
9999
/// Returns [`FilesystemStoreV2Error::V1DataDetected`] if the data directory already exists
100-
/// and contains files at the top level, which would indicate it was previously used by a
101-
/// [`FilesystemStore`] (v1). The v2 store expects only directories (namespaces) at the top
102-
/// level.
100+
/// and contains files where v2 expects namespace directories, which would indicate it was
101+
/// previously used by a [`FilesystemStore`] (v1). The v2 store expects only directories at
102+
/// the top level and one level down.
103103
///
104104
/// [`FilesystemStore`]: crate::fs_store::v1::FilesystemStore
105105
pub fn new(data_dir: PathBuf) -> Result<Self, FilesystemStoreV2Error> {
106106
if data_dir.exists() {
107107
for entry in fs::read_dir(&data_dir)? {
108108
let entry = entry?;
109-
if entry.file_type()?.is_file() {
109+
let file_type = entry.file_type()?;
110+
if file_type.is_file() {
110111
return Err(FilesystemStoreV2Error::V1DataDetected(entry.path()));
111112
}
113+
114+
if file_type.is_dir() {
115+
for child_entry in fs::read_dir(entry.path())? {
116+
let child_entry = child_entry?;
117+
if child_entry.file_type()?.is_file() {
118+
return Err(FilesystemStoreV2Error::V1DataDetected(child_entry.path()));
119+
}
120+
}
121+
}
112122
}
113123
}
114124

@@ -699,6 +709,7 @@ mod tests {
699709
fs::create_dir_all(&temp_path).unwrap();
700710

701711
// Create a file at the top level, as v1 would for an empty primary namespace
712+
// and an empty secondary namespace.
702713
fs::write(temp_path.join("some_key"), b"data").unwrap();
703714

704715
// V2 construction should fail
@@ -713,13 +724,59 @@ mod tests {
713724
// Clean up
714725
let _ = fs::remove_dir_all(&temp_path);
715726

727+
// Create a file one level down, as v1 would for a non-empty primary namespace
728+
// and an empty secondary namespace.
729+
fs::create_dir_all(temp_path.join("some_namespace")).unwrap();
730+
fs::write(temp_path.join("some_namespace").join("some_key"), b"data").unwrap();
731+
732+
match FilesystemStoreV2::new(temp_path.clone()) {
733+
Err(FilesystemStoreV2Error::V1DataDetected(path)) => {
734+
assert_eq!(path, temp_path.join("some_namespace").join("some_key"));
735+
},
736+
Err(err) => panic!("Expected V1DataDetected, got {:?}", err),
737+
Ok(_) => panic!("Expected error for directory with files one level down"),
738+
}
739+
740+
let _ = fs::remove_dir_all(&temp_path);
741+
742+
// A v1 write with an empty primary namespace and non-empty secondary namespace
743+
// is rejected by the KVStore API, but its filesystem layout would be the same
744+
// one-level shape.
745+
fs::create_dir_all(temp_path.join("some_secondary_namespace")).unwrap();
746+
fs::write(temp_path.join("some_secondary_namespace").join("some_key"), b"data").unwrap();
747+
748+
match FilesystemStoreV2::new(temp_path.clone()) {
749+
Err(FilesystemStoreV2Error::V1DataDetected(path)) => {
750+
assert_eq!(path, temp_path.join("some_secondary_namespace").join("some_key"));
751+
},
752+
Err(err) => panic!("Expected V1DataDetected, got {:?}", err),
753+
Ok(_) => panic!("Expected error for directory with files one level down"),
754+
}
755+
756+
let _ = fs::remove_dir_all(&temp_path);
757+
716758
// An empty directory should succeed
717759
fs::create_dir_all(&temp_path).unwrap();
718760
let result = FilesystemStoreV2::new(temp_path.clone());
719761
assert!(result.is_ok());
720762

721-
// A directory with only subdirectories should succeed
722-
fs::create_dir_all(temp_path.join("some_namespace")).unwrap();
763+
// A directory with only namespace subdirectories should succeed
764+
fs::create_dir_all(temp_path.join("some_namespace").join("some_sub_namespace")).unwrap();
765+
let result = FilesystemStoreV2::new(temp_path.clone());
766+
assert!(result.is_ok());
767+
768+
// V1 data with non-empty primary and secondary namespaces has the same filesystem
769+
// layout as valid v2 data, so construction must not reject this shape.
770+
let fs_store = result.unwrap();
771+
KVStoreSync::write(
772+
&fs_store,
773+
"some_namespace",
774+
"some_sub_namespace",
775+
"some_key",
776+
b"data".to_vec(),
777+
)
778+
.unwrap();
779+
723780
let result = FilesystemStoreV2::new(temp_path);
724781
assert!(result.is_ok());
725782
}

0 commit comments

Comments
 (0)