@@ -24,7 +24,7 @@ use bitcoin::Network;
2424use lightning:: impl_writeable_tlv_based_enum;
2525use lightning:: io:: { self , Error , ErrorKind } ;
2626use lightning:: sign:: { EntropySource as LdkEntropySource , RandomBytes } ;
27- use lightning:: util:: persist:: KVStore ;
27+ use lightning:: util:: persist:: { KVStore , PageToken , PaginatedKVStore , PaginatedListResponse } ;
2828use lightning:: util:: ser:: { Readable , Writeable } ;
2929use prost:: Message ;
3030use 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+
296322impl 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