Skip to content

Commit d3f29b0

Browse files
committed
Add PaginatedKVStore support to VssStore
1 parent 1b2de7c commit d3f29b0

File tree

1 file changed

+160
-1
lines changed

1 file changed

+160
-1
lines changed

src/io/vss_store.rs

Lines changed: 160 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, KVStoreSync};
27+
use lightning::util::persist::{
28+
KVStore, KVStoreSync, PageToken, PaginatedKVStore, PaginatedKVStoreSync, PaginatedListResponse,
29+
};
2830
use lightning::util::ser::{Readable, Writeable};
2931
use prost::Message;
3032
use vss_client::client::VssClient;
@@ -377,6 +379,52 @@ impl KVStore for VssStore {
377379
}
378380
}
379381

382+
impl PaginatedKVStoreSync for VssStore {
383+
fn list_paginated(
384+
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
385+
) -> io::Result<PaginatedListResponse> {
386+
let internal_runtime = self.internal_runtime.as_ref().ok_or_else(|| {
387+
debug_assert!(false, "Failed to access internal runtime");
388+
let msg = format!("Failed to access internal runtime");
389+
Error::new(ErrorKind::Other, msg)
390+
})?;
391+
let primary_namespace = primary_namespace.to_string();
392+
let secondary_namespace = secondary_namespace.to_string();
393+
let inner = Arc::clone(&self.inner);
394+
let fut = async move {
395+
inner
396+
.list_paginated_internal(
397+
&inner.blocking_client,
398+
primary_namespace,
399+
secondary_namespace,
400+
page_token,
401+
)
402+
.await
403+
};
404+
tokio::task::block_in_place(move || internal_runtime.block_on(fut))
405+
}
406+
}
407+
408+
impl PaginatedKVStore for VssStore {
409+
fn list_paginated(
410+
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
411+
) -> impl Future<Output = Result<PaginatedListResponse, io::Error>> + 'static + Send {
412+
let primary_namespace = primary_namespace.to_string();
413+
let secondary_namespace = secondary_namespace.to_string();
414+
let inner = Arc::clone(&self.inner);
415+
async move {
416+
inner
417+
.list_paginated_internal(
418+
&inner.async_client,
419+
primary_namespace,
420+
secondary_namespace,
421+
page_token,
422+
)
423+
.await
424+
}
425+
}
426+
}
427+
380428
impl Drop for VssStore {
381429
fn drop(&mut self) {
382430
let internal_runtime = self.internal_runtime.take();
@@ -636,6 +684,36 @@ impl VssStoreInner {
636684
Ok(keys)
637685
}
638686

687+
async fn list_paginated_internal(
688+
&self, client: &VssClient<CustomRetryPolicy>, primary_namespace: String,
689+
secondary_namespace: String, page_token: Option<PageToken>,
690+
) -> io::Result<PaginatedListResponse> {
691+
check_namespace_key_validity(
692+
&primary_namespace,
693+
&secondary_namespace,
694+
None,
695+
"list_paginated",
696+
)?;
697+
698+
const PAGE_SIZE: i32 = 50;
699+
700+
let vss_page_token = page_token.map(|t| t.to_string());
701+
let (keys, next_page_token) = self
702+
.list_keys(
703+
client,
704+
&primary_namespace,
705+
&secondary_namespace,
706+
vss_page_token,
707+
Some(PAGE_SIZE),
708+
)
709+
.await?;
710+
711+
// VSS can use empty string to signal the last page
712+
let next_page_token = next_page_token.filter(|t| !t.is_empty()).map(PageToken::new);
713+
714+
Ok(PaginatedListResponse { keys, next_page_token })
715+
}
716+
639717
async fn execute_locked_write<
640718
F: Future<Output = Result<(), lightning::io::Error>>,
641719
FN: FnOnce() -> F,
@@ -1042,4 +1120,85 @@ mod tests {
10421120
do_read_write_remove_list_persist(&vss_store);
10431121
drop(vss_store)
10441122
}
1123+
1124+
#[test]
1125+
fn vss_paginated_listing() {
1126+
let store = build_vss_store();
1127+
let ns = "test_paginated";
1128+
let sub = "listing";
1129+
let num_entries = 5;
1130+
1131+
for i in 0..num_entries {
1132+
let key = format!("key_{:04}", i);
1133+
let data = vec![i as u8; 32];
1134+
KVStoreSync::write(&store, ns, sub, &key, data).unwrap();
1135+
}
1136+
1137+
let mut all_keys = Vec::new();
1138+
let mut page_token = None;
1139+
1140+
loop {
1141+
let response =
1142+
PaginatedKVStoreSync::list_paginated(&store, ns, sub, page_token).unwrap();
1143+
all_keys.extend(response.keys);
1144+
match response.next_page_token {
1145+
Some(token) => page_token = Some(token),
1146+
_ => break,
1147+
}
1148+
}
1149+
1150+
assert_eq!(all_keys.len(), num_entries);
1151+
1152+
// Verify no duplicates
1153+
let mut unique = all_keys.clone();
1154+
unique.sort();
1155+
unique.dedup();
1156+
assert_eq!(unique.len(), num_entries);
1157+
}
1158+
1159+
#[test]
1160+
fn vss_paginated_empty_namespace() {
1161+
let store = build_vss_store();
1162+
let response =
1163+
PaginatedKVStoreSync::list_paginated(&store, "nonexistent", "ns", None).unwrap();
1164+
assert!(response.keys.is_empty());
1165+
assert!(response.next_page_token.is_none());
1166+
}
1167+
1168+
#[test]
1169+
fn vss_paginated_removal() {
1170+
let store = build_vss_store();
1171+
let ns = "test_paginated";
1172+
let sub = "removal";
1173+
1174+
KVStoreSync::write(&store, ns, sub, "a", vec![1u8; 8]).unwrap();
1175+
KVStoreSync::write(&store, ns, sub, "b", vec![2u8; 8]).unwrap();
1176+
KVStoreSync::write(&store, ns, sub, "c", vec![3u8; 8]).unwrap();
1177+
1178+
KVStoreSync::remove(&store, ns, sub, "b", false).unwrap();
1179+
1180+
let response = PaginatedKVStoreSync::list_paginated(&store, ns, sub, None).unwrap();
1181+
assert_eq!(response.keys.len(), 2);
1182+
assert!(response.keys.contains(&"a".to_string()));
1183+
assert!(!response.keys.contains(&"b".to_string()));
1184+
assert!(response.keys.contains(&"c".to_string()));
1185+
}
1186+
1187+
#[test]
1188+
fn vss_paginated_namespace_isolation() {
1189+
let store = build_vss_store();
1190+
1191+
KVStoreSync::write(&store, "ns_a", "sub", "key_1", vec![1u8; 8]).unwrap();
1192+
KVStoreSync::write(&store, "ns_a", "sub", "key_2", vec![2u8; 8]).unwrap();
1193+
KVStoreSync::write(&store, "ns_b", "sub", "key_3", vec![3u8; 8]).unwrap();
1194+
1195+
let response = PaginatedKVStoreSync::list_paginated(&store, "ns_a", "sub", None).unwrap();
1196+
assert_eq!(response.keys.len(), 2);
1197+
assert!(response.keys.contains(&"key_1".to_string()));
1198+
assert!(response.keys.contains(&"key_2".to_string()));
1199+
1200+
let response = PaginatedKVStoreSync::list_paginated(&store, "ns_b", "sub", None).unwrap();
1201+
assert_eq!(response.keys.len(), 1);
1202+
assert!(response.keys.contains(&"key_3".to_string()));
1203+
}
10451204
}

0 commit comments

Comments
 (0)