Skip to content

Commit 5429331

Browse files
committed
Add PaginatedKVStore support to VssStore
1 parent 2c8b658 commit 5429331

1 file changed

Lines changed: 142 additions & 4 deletions

File tree

src/io/vss_store.rs

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ 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;
27+
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
2828
use lightning::util::ser::{Readable, Writeable};
2929
use prost::Message;
3030
use vss_client::client::VssClient;
@@ -70,6 +70,8 @@ impl_writeable_tlv_based_enum!(VssSchemaVersion,
7070
(1, V1) => {},
7171
);
7272

73+
const PAGE_SIZE: i32 = 50;
74+
7375
const VSS_HARDENED_CHILD_INDEX: u32 = 877;
7476
const VSS_SIGS_AUTH_HARDENED_CHILD_INDEX: u32 = 139;
7577
const VSS_SCHEMA_VERSION_KEY: &str = "vss_schema_version";
@@ -293,6 +295,32 @@ impl KVStore for VssStore {
293295
}
294296
}
295297

298+
impl PaginatedKVStore for VssStore {
299+
fn list_paginated(
300+
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
301+
) -> impl Future<Output = Result<PaginatedListResponse, io::Error>> + 'static + Send {
302+
let primary_namespace = primary_namespace.to_string();
303+
let secondary_namespace = secondary_namespace.to_string();
304+
let inner = Arc::clone(&self.inner);
305+
let runtime = self.internal_runtime();
306+
async move {
307+
let task = runtime.spawn(async move {
308+
inner
309+
.list_paginated_internal(
310+
&inner.async_client,
311+
primary_namespace,
312+
secondary_namespace,
313+
page_token,
314+
)
315+
.await
316+
});
317+
task.await.map_err(|e| {
318+
io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e))
319+
})?
320+
}
321+
}
322+
}
323+
296324
impl Drop for VssStore {
297325
fn drop(&mut self) {
298326
if let Some(runtime) = self.internal_runtime.take() {
@@ -393,9 +421,8 @@ impl VssStoreInner {
393421

394422
async fn list_keys(
395423
&self, client: &VssClient<CustomRetryPolicy>, primary_namespace: &str,
396-
secondary_namespace: &str, page_token: Option<String>, page_size: Option<i32>,
424+
secondary_namespace: &str, key_prefix: String, page_token: Option<String>, page_size: Option<i32>,
397425
) -> io::Result<(Vec<String>, Option<String>)> {
398-
let key_prefix = self.build_obfuscated_prefix(primary_namespace, secondary_namespace);
399426
let request = ListKeyVersionsRequest {
400427
store_id: self.store_id.clone(),
401428
key_prefix: Some(key_prefix),
@@ -542,11 +569,12 @@ impl VssStoreInner {
542569
) -> io::Result<Vec<String>> {
543570
check_namespace_key_validity(&primary_namespace, &secondary_namespace, None, "list")?;
544571

572+
let key_prefix = self.build_obfuscated_prefix(&primary_namespace, &secondary_namespace);
545573
let mut page_token: Option<String> = None;
546574
let mut keys = vec![];
547575
loop {
548576
let (page_keys, next_page_token) = self
549-
.list_keys(client, &primary_namespace, &secondary_namespace, page_token, None)
577+
.list_keys(client, &primary_namespace, &secondary_namespace, key_prefix.clone(), page_token, None)
550578
.await?;
551579
keys.extend(page_keys);
552580
match next_page_token {
@@ -557,6 +585,35 @@ impl VssStoreInner {
557585
Ok(keys)
558586
}
559587

588+
async fn list_paginated_internal(
589+
&self, client: &VssClient<CustomRetryPolicy>, primary_namespace: String,
590+
secondary_namespace: String, page_token: Option<PageToken>,
591+
) -> io::Result<PaginatedListResponse> {
592+
check_namespace_key_validity(
593+
&primary_namespace,
594+
&secondary_namespace,
595+
None,
596+
"list_paginated",
597+
)?;
598+
599+
let key_prefix = self.build_obfuscated_prefix(&primary_namespace, &secondary_namespace);
600+
let vss_page_token = page_token.map(|t| t.to_string());
601+
let (keys, next_page_token) = self
602+
.list_keys(
603+
client,
604+
&primary_namespace,
605+
&secondary_namespace,
606+
key_prefix,
607+
vss_page_token,
608+
Some(PAGE_SIZE),
609+
)
610+
.await?;
611+
612+
let next_page_token = next_page_token.map(PageToken::new);
613+
614+
Ok(PaginatedListResponse { keys, next_page_token })
615+
}
616+
560617
async fn execute_locked_write<
561618
F: Future<Output = Result<(), lightning::io::Error>>,
562619
FN: FnOnce() -> F,
@@ -972,4 +1029,85 @@ mod tests {
9721029
do_read_write_remove_list_persist(&vss_store).await;
9731030
drop(vss_store)
9741031
}
1032+
1033+
#[tokio::test]
1034+
async fn vss_paginated_listing() {
1035+
let store = build_vss_store();
1036+
let ns = "test_paginated";
1037+
let sub = "listing";
1038+
let num_entries = 5;
1039+
1040+
for i in 0..num_entries {
1041+
let key = format!("key_{:04}", i);
1042+
let data = vec![i as u8; 32];
1043+
KVStore::write(&store, ns, sub, &key, data).await.unwrap();
1044+
}
1045+
1046+
let mut all_keys = Vec::new();
1047+
let mut page_token = None;
1048+
1049+
loop {
1050+
let response =
1051+
PaginatedKVStore::list_paginated(&store, ns, sub, page_token).await.unwrap();
1052+
all_keys.extend(response.keys);
1053+
match response.next_page_token {
1054+
Some(token) => page_token = Some(token),
1055+
_ => break,
1056+
}
1057+
}
1058+
1059+
assert_eq!(all_keys.len(), num_entries);
1060+
1061+
// Verify no duplicates
1062+
let mut unique = all_keys.clone();
1063+
unique.sort();
1064+
unique.dedup();
1065+
assert_eq!(unique.len(), num_entries);
1066+
}
1067+
1068+
#[tokio::test]
1069+
async fn vss_paginated_empty_namespace() {
1070+
let store = build_vss_store();
1071+
let response =
1072+
PaginatedKVStore::list_paginated(&store, "nonexistent", "ns", None).await.unwrap();
1073+
assert!(response.keys.is_empty());
1074+
assert!(response.next_page_token.is_none());
1075+
}
1076+
1077+
#[tokio::test]
1078+
async fn vss_paginated_removal() {
1079+
let store = build_vss_store();
1080+
let ns = "test_paginated";
1081+
let sub = "removal";
1082+
1083+
KVStore::write(&store, ns, sub, "a", vec![1u8; 8]).await.unwrap();
1084+
KVStore::write(&store, ns, sub, "b", vec![2u8; 8]).await.unwrap();
1085+
KVStore::write(&store, ns, sub, "c", vec![3u8; 8]).await.unwrap();
1086+
1087+
KVStore::remove(&store, ns, sub, "b", false).await.unwrap();
1088+
1089+
let response = PaginatedKVStore::list_paginated(&store, ns, sub, None).await.unwrap();
1090+
assert_eq!(response.keys.len(), 2);
1091+
assert!(response.keys.contains(&"a".to_string()));
1092+
assert!(!response.keys.contains(&"b".to_string()));
1093+
assert!(response.keys.contains(&"c".to_string()));
1094+
}
1095+
1096+
#[tokio::test]
1097+
async fn vss_paginated_namespace_isolation() {
1098+
let store = build_vss_store();
1099+
1100+
KVStore::write(&store, "ns_a", "sub", "key_1", vec![1u8; 8]).await.unwrap();
1101+
KVStore::write(&store, "ns_a", "sub", "key_2", vec![2u8; 8]).await.unwrap();
1102+
KVStore::write(&store, "ns_b", "sub", "key_3", vec![3u8; 8]).await.unwrap();
1103+
1104+
let response = PaginatedKVStore::list_paginated(&store, "ns_a", "sub", None).await.unwrap();
1105+
assert_eq!(response.keys.len(), 2);
1106+
assert!(response.keys.contains(&"key_1".to_string()));
1107+
assert!(response.keys.contains(&"key_2".to_string()));
1108+
1109+
let response = PaginatedKVStore::list_paginated(&store, "ns_b", "sub", None).await.unwrap();
1110+
assert_eq!(response.keys.len(), 1);
1111+
assert!(response.keys.contains(&"key_3".to_string()));
1112+
}
9751113
}

0 commit comments

Comments
 (0)