Skip to content

Commit f51bf20

Browse files
committed
Add PaginatedKVStore support to VssStore
1 parent 7ebde03 commit f51bf20

1 file changed

Lines changed: 137 additions & 1 deletion

File tree

src/io/vss_store.rs

Lines changed: 137 additions & 1 deletion
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;
@@ -293,6 +293,32 @@ impl KVStore for VssStore {
293293
}
294294
}
295295

296+
impl PaginatedKVStore for VssStore {
297+
fn list_paginated(
298+
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
299+
) -> impl Future<Output = Result<PaginatedListResponse, io::Error>> + 'static + Send {
300+
let primary_namespace = primary_namespace.to_string();
301+
let secondary_namespace = secondary_namespace.to_string();
302+
let inner = Arc::clone(&self.inner);
303+
let runtime = self.internal_runtime();
304+
async move {
305+
let task = runtime.spawn(async move {
306+
inner
307+
.list_paginated_internal(
308+
&inner.async_client,
309+
primary_namespace,
310+
secondary_namespace,
311+
page_token,
312+
)
313+
.await
314+
});
315+
task.await.map_err(|e| {
316+
io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e))
317+
})?
318+
}
319+
}
320+
}
321+
296322
impl Drop for VssStore {
297323
fn drop(&mut self) {
298324
if let Some(runtime) = self.internal_runtime.take() {
@@ -557,6 +583,35 @@ impl VssStoreInner {
557583
Ok(keys)
558584
}
559585

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

0 commit comments

Comments
 (0)