Skip to content

Commit 18fa93a

Browse files
committed
Implement MigratableKVStore for VSS
This was unimplemented for the VSS kv store. Useful if the user wants to migrate to a different database and keeps VSS aligned with the other persistent stores. AI-assisted-by: OpenAI Codex
1 parent 43078aa commit 18fa93a

1 file changed

Lines changed: 121 additions & 1 deletion

File tree

src/io/vss_store.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use bitcoin::Network;
2424
use lightning::impl_writeable_tlv_based_enum;
2525
use lightning::io::{self, Error, ErrorKind};
2626
use lightning::sign::{EntropySource as LdkEntropySource, RandomBytes};
27-
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
27+
use lightning::util::persist::{
28+
KVStore, MigratableKVStore, PageToken, PaginatedKVStore, PaginatedListResponse,
29+
};
2830
use lightning::util::ser::{Readable, Writeable};
2931
use prost::Message;
3032
use vss_client::client::VssClient;
@@ -321,6 +323,22 @@ impl PaginatedKVStore for VssStore {
321323
}
322324
}
323325

326+
impl MigratableKVStore for VssStore {
327+
fn list_all_keys(
328+
&self,
329+
) -> impl Future<Output = Result<Vec<(String, String, String)>, io::Error>> + 'static + Send {
330+
let inner = Arc::clone(&self.inner);
331+
let runtime = self.internal_runtime();
332+
async move {
333+
let task = runtime
334+
.spawn(async move { inner.list_all_keys_internal(&inner.async_client).await });
335+
task.await.map_err(|e| {
336+
io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e))
337+
})?
338+
}
339+
}
340+
}
341+
324342
impl Drop for VssStore {
325343
fn drop(&mut self) {
326344
if let Some(runtime) = self.internal_runtime.take() {
@@ -422,6 +440,41 @@ impl VssStoreInner {
422440
Ok(actual_key)
423441
}
424442

443+
fn extract_namespaces(&self, unified_key: &str) -> io::Result<(String, String)> {
444+
if self.schema_version == VssSchemaVersion::V1 {
445+
let mut parts = unified_key.splitn(2, '#');
446+
let obfuscated_namespace = parts.next();
447+
let _obfuscated_key = parts.next();
448+
match (obfuscated_namespace, _obfuscated_key) {
449+
(Some(obfuscated_namespace), Some(_obfuscated_key)) => {
450+
let namespace = self.key_obfuscator.deobfuscate(obfuscated_namespace)?;
451+
let mut namespace_parts = namespace.splitn(2, '#');
452+
let primary_namespace = namespace_parts.next();
453+
let secondary_namespace = namespace_parts.next();
454+
match (primary_namespace, secondary_namespace) {
455+
(Some(primary_namespace), Some(secondary_namespace)) => {
456+
Ok((primary_namespace.to_string(), secondary_namespace.to_string()))
457+
},
458+
_ => Err(Error::new(ErrorKind::InvalidData, "Invalid namespace format")),
459+
}
460+
},
461+
_ => Err(Error::new(ErrorKind::InvalidData, "Invalid key format")),
462+
}
463+
} else {
464+
// Default to V0 schema.
465+
let mut parts = unified_key.splitn(3, '#');
466+
let primary_namespace = parts.next();
467+
let secondary_namespace = parts.next();
468+
match (primary_namespace, secondary_namespace) {
469+
(Some(_obfuscated_key), None) => Ok(("".to_string(), "".to_string())),
470+
(Some(primary_namespace), Some(secondary_namespace)) => {
471+
Ok((primary_namespace.to_string(), secondary_namespace.to_string()))
472+
},
473+
_ => Err(Error::new(ErrorKind::InvalidData, "Invalid key format")),
474+
}
475+
}
476+
}
477+
425478
async fn list_keys(
426479
&self, client: &VssClient<CustomRetryPolicy>, primary_namespace: &str,
427480
secondary_namespace: &str, key_prefix: String, page_token: Option<String>,
@@ -625,6 +678,52 @@ impl VssStoreInner {
625678
Ok(PaginatedListResponse { keys, next_page_token })
626679
}
627680

681+
async fn list_all_keys_internal(
682+
&self, client: &VssClient<CustomRetryPolicy>,
683+
) -> io::Result<Vec<(String, String, String)>> {
684+
let mut page_token: Option<String> = None;
685+
let mut keys = vec![];
686+
loop {
687+
let request = ListKeyVersionsRequest {
688+
store_id: self.store_id.clone(),
689+
key_prefix: None,
690+
page_token,
691+
page_size: Some(PAGE_SIZE),
692+
};
693+
694+
let response = client.list_key_versions(&request).await.map_err(|e| {
695+
let msg = format!("Failed to list all keys: {}", e);
696+
Error::new(ErrorKind::Other, msg)
697+
})?;
698+
699+
for kv in response.key_versions {
700+
let (primary_namespace, secondary_namespace) = self.extract_namespaces(&kv.key)?;
701+
let key = match self.extract_key(&kv.key) {
702+
Ok(key) => key,
703+
Err(_)
704+
if self.schema_version == VssSchemaVersion::V0 && !kv.key.contains('#') =>
705+
{
706+
self.key_obfuscator.deobfuscate(&kv.key)?
707+
},
708+
Err(e) => return Err(e),
709+
};
710+
if primary_namespace.is_empty()
711+
&& secondary_namespace.is_empty()
712+
&& key == VSS_SCHEMA_VERSION_KEY
713+
{
714+
continue;
715+
}
716+
keys.push((primary_namespace, secondary_namespace, key));
717+
}
718+
719+
match response.next_page_token.filter(|t| !t.is_empty()) {
720+
Some(t) => page_token = Some(t),
721+
None => break,
722+
}
723+
}
724+
Ok(keys)
725+
}
726+
628727
async fn execute_locked_write<
629728
F: Future<Output = Result<(), lightning::io::Error>>,
630729
FN: FnOnce() -> F,
@@ -1041,6 +1140,27 @@ mod tests {
10411140
drop(vss_store)
10421141
}
10431142

1143+
#[tokio::test]
1144+
async fn vss_list_all_keys() {
1145+
let store = build_vss_store();
1146+
1147+
KVStore::write(&store, "ns_a", "sub_a", "key_a", vec![1u8]).await.unwrap();
1148+
KVStore::write(&store, "ns_a", "sub_b", "key_b", vec![2u8]).await.unwrap();
1149+
KVStore::write(&store, "ns_b", "", "key_c", vec![3u8]).await.unwrap();
1150+
1151+
let mut keys = MigratableKVStore::list_all_keys(&store).await.unwrap();
1152+
keys.sort();
1153+
1154+
assert_eq!(
1155+
keys,
1156+
vec![
1157+
("ns_a".to_string(), "sub_a".to_string(), "key_a".to_string()),
1158+
("ns_a".to_string(), "sub_b".to_string(), "key_b".to_string()),
1159+
("ns_b".to_string(), "".to_string(), "key_c".to_string()),
1160+
]
1161+
);
1162+
}
1163+
10441164
#[tokio::test]
10451165
async fn vss_paginated_listing() {
10461166
let store = build_vss_store();

0 commit comments

Comments
 (0)