@@ -212,6 +212,54 @@ impl TransactionCounterTrait for RedisTransactionCounter {
212212 debug ! ( value = %value, "counter set" ) ;
213213 Ok ( ( ) )
214214 }
215+
216+ async fn drop_all_entries ( & self ) -> Result < ( ) , RepositoryError > {
217+ let mut conn = self
218+ . get_connection ( self . connections . primary ( ) , "drop_all_entries" )
219+ . await ?;
220+
221+ let pattern = format ! ( "{}:{}:*" , self . key_prefix, COUNTER_PREFIX ) ;
222+ debug ! ( pattern = %pattern, "dropping all transaction counter entries" ) ;
223+
224+ // Phase 1: Collect all matching keys without mutating the keyspace.
225+ // Deleting during SCAN can cause hash table rehashing, which may skip keys.
226+ let mut cursor: u64 = 0 ;
227+ let mut all_keys: Vec < String > = Vec :: new ( ) ;
228+
229+ loop {
230+ let ( next_cursor, keys) : ( u64 , Vec < String > ) = redis:: cmd ( "SCAN" )
231+ . cursor_arg ( cursor)
232+ . arg ( "MATCH" )
233+ . arg ( & pattern)
234+ . arg ( "COUNT" )
235+ . arg ( 100 )
236+ . query_async ( & mut conn)
237+ . await
238+ . map_err ( |e| self . map_redis_error ( e, "drop_all_entries_scan" ) ) ?;
239+
240+ all_keys. extend ( keys) ;
241+
242+ cursor = next_cursor;
243+ if cursor == 0 {
244+ break ;
245+ }
246+ }
247+
248+ // Phase 2: Batch delete all collected keys.
249+ if !all_keys. is_empty ( ) {
250+ let mut pipe = redis:: pipe ( ) ;
251+ pipe. atomic ( ) ;
252+ for key in & all_keys {
253+ pipe. del ( key) ;
254+ }
255+ pipe. exec_async ( & mut conn)
256+ . await
257+ . map_err ( |e| self . map_redis_error ( e, "drop_all_entries_delete" ) ) ?;
258+ }
259+
260+ debug ! ( total_deleted = %all_keys. len( ) , "dropped all transaction counter entries" ) ;
261+ Ok ( ( ) )
262+ }
215263}
216264
217265#[ cfg( test) ]
@@ -222,6 +270,10 @@ mod tests {
222270 use uuid:: Uuid ;
223271
224272 async fn setup_test_repo ( ) -> RedisTransactionCounter {
273+ setup_test_repo_with_prefix ( "test_counter" ) . await
274+ }
275+
276+ async fn setup_test_repo_with_prefix ( prefix : & str ) -> RedisTransactionCounter {
225277 let redis_url =
226278 std:: env:: var ( "REDIS_URL" ) . unwrap_or_else ( |_| "redis://127.0.0.1:6379" . to_string ( ) ) ;
227279 let cfg = deadpool_redis:: Config :: from_url ( & redis_url) ;
@@ -235,7 +287,7 @@ mod tests {
235287 ) ;
236288 let connections = Arc :: new ( RedisConnections :: new_single_pool ( pool) ) ;
237289
238- RedisTransactionCounter :: new ( connections, "test_counter" . to_string ( ) )
290+ RedisTransactionCounter :: new ( connections, prefix . to_string ( ) )
239291 . expect ( "Failed to create Redis transaction counter" )
240292 }
241293
@@ -369,6 +421,35 @@ mod tests {
369421 assert_eq ! ( repo. get( & relayer_2, & address_1) . await . unwrap( ) , Some ( 300 ) ) ;
370422 }
371423
424+ #[ tokio:: test]
425+ #[ ignore = "Requires active Redis instance" ]
426+ async fn test_drop_all_entries ( ) {
427+ let prefix = format ! ( "test_drop_{}" , uuid:: Uuid :: new_v4( ) ) ;
428+ let repo = setup_test_repo_with_prefix ( & prefix) . await ;
429+ let relayer_1 = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
430+ let relayer_2 = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
431+ let address_1 = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
432+ let address_2 = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
433+
434+ // Set up multiple counters
435+ repo. set ( & relayer_1, & address_1, 100 ) . await . unwrap ( ) ;
436+ repo. set ( & relayer_1, & address_2, 200 ) . await . unwrap ( ) ;
437+ repo. set ( & relayer_2, & address_1, 300 ) . await . unwrap ( ) ;
438+
439+ // Verify they exist
440+ assert_eq ! ( repo. get( & relayer_1, & address_1) . await . unwrap( ) , Some ( 100 ) ) ;
441+ assert_eq ! ( repo. get( & relayer_1, & address_2) . await . unwrap( ) , Some ( 200 ) ) ;
442+ assert_eq ! ( repo. get( & relayer_2, & address_1) . await . unwrap( ) , Some ( 300 ) ) ;
443+
444+ // Drop all
445+ repo. drop_all_entries ( ) . await . unwrap ( ) ;
446+
447+ // Verify all are gone
448+ assert_eq ! ( repo. get( & relayer_1, & address_1) . await . unwrap( ) , None ) ;
449+ assert_eq ! ( repo. get( & relayer_1, & address_2) . await . unwrap( ) , None ) ;
450+ assert_eq ! ( repo. get( & relayer_2, & address_1) . await . unwrap( ) , None ) ;
451+ }
452+
372453 #[ tokio:: test]
373454 #[ ignore = "Requires active Redis instance" ]
374455 async fn test_concurrent_get_and_increment ( ) {
0 commit comments