@@ -24,7 +24,9 @@ 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 , KVStoreSync } ;
27+ use lightning:: util:: persist:: {
28+ KVStore , KVStoreSync , PageToken , PaginatedKVStore , PaginatedKVStoreSync , PaginatedListResponse ,
29+ } ;
2830use lightning:: util:: ser:: { Readable , Writeable } ;
2931use prost:: Message ;
3032use 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+
380428impl 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