@@ -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 ;
@@ -70,6 +70,8 @@ impl_writeable_tlv_based_enum!(VssSchemaVersion,
7070 ( 1 , V1 ) => { } ,
7171) ;
7272
73+ const PAGE_SIZE : i32 = 50 ;
74+
7375const VSS_HARDENED_CHILD_INDEX : u32 = 877 ;
7476const VSS_SIGS_AUTH_HARDENED_CHILD_INDEX : u32 = 139 ;
7577const 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+
296324impl 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