@@ -394,8 +394,6 @@ impl masternode::MasternodeStateStorage for DiskStorageManager {
394394
395395#[ cfg( test) ]
396396mod tests {
397- use crate :: ChainState ;
398-
399397 use super :: * ;
400398 use dashcore:: Header as BlockHeader ;
401399 use tempfile:: { tempdir, TempDir } ;
@@ -404,124 +402,182 @@ mod tests {
404402 async fn test_load_headers ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
405403 // Create a temporary directory for the test
406404 let temp_dir = TempDir :: new ( ) ?;
407- let mut storage = DiskStorageManager :: new ( temp_dir. path ( ) . to_path_buf ( ) )
408- . await
409- . expect ( "Unable to create storage" ) ;
405+ let mut storage =
406+ DiskStorageManager :: new ( temp_dir. path ( ) ) . await . expect ( "Unable to create storage" ) ;
410407
411- // Create a test header
412- let test_header = BlockHeader :: dummy ( 1 ) ;
408+ let headers = BlockHeader :: dummy_batch ( 0 ..60_000 ) ;
413409
414- // Store just one header
415- storage. store_headers ( & [ test_header ] ) . await ? ;
410+ storage . store_headers ( & headers [ 0 .. 0 ] ) . await . expect ( "Should handle empty header batch" ) ;
411+ assert_eq ! ( storage. get_tip_height ( ) . await , None ) ;
416412
413+ storage. store_headers ( & headers[ 0 ..1 ] ) . await . expect ( "Failed to store headers" ) ;
417414 let loaded_headers = storage. load_headers ( 0 ..1 ) . await ?;
418-
419- // Should only get back the one header we stored
420415 assert_eq ! ( loaded_headers. len( ) , 1 ) ;
421- assert_eq ! ( loaded_headers[ 0 ] , test_header) ;
416+ assert_eq ! ( loaded_headers[ 0 ] , headers[ 0 ] ) ;
417+
418+ storage. store_headers ( & headers[ 1 ..100 ] ) . await . expect ( "Failed to store headers" ) ;
419+ let loaded_headers = storage. load_headers ( 50 ..60 ) . await . unwrap ( ) ;
420+ assert_eq ! ( loaded_headers. len( ) , 10 ) ;
421+ assert_eq ! ( & loaded_headers, & headers[ 50 ..60 ] ) ;
422+
423+ storage. store_headers ( & headers[ 100 ..headers. len ( ) ] ) . await . expect ( "Failed to store headers" ) ;
424+
425+ let tip_height = storage. get_tip_height ( ) . await . unwrap ( ) ;
426+ let tip_header = storage. get_header ( tip_height) . await . unwrap ( ) . unwrap ( ) ;
427+ let expected_header = & headers[ headers. len ( ) - 1 ] ;
428+ assert_eq ! ( tip_header, * expected_header) ;
429+
430+ let non_existing_height = tip_height + 1 ;
431+ let non_existing_header = storage. get_header ( non_existing_height) . await . unwrap ( ) ;
432+ assert ! ( non_existing_header. is_none( ) ) ;
433+
434+ storage. shutdown ( ) . await ;
435+ drop ( storage) ;
436+ let storage =
437+ DiskStorageManager :: new ( temp_dir. path ( ) ) . await . expect ( "Unable to open storage" ) ;
438+
439+ let loaded_headers = storage. load_headers ( 49_999 ..50_002 ) . await . unwrap ( ) ;
440+ assert_eq ! ( loaded_headers. len( ) , 3 ) ;
441+ assert_eq ! ( & loaded_headers, & headers[ 49_999 ..50_002 ] ) ;
422442
423443 Ok ( ( ) )
424444 }
425445
426446 #[ tokio:: test]
427447 async fn test_checkpoint_storage_indexing ( ) -> StorageResult < ( ) > {
428448 let temp_dir = tempdir ( ) . expect ( "Failed to create temp dir" ) ;
429- let mut storage = DiskStorageManager :: new ( temp_dir. path ( ) . to_path_buf ( ) ) . await ?;
449+ let mut storage = DiskStorageManager :: new ( temp_dir. path ( ) ) . await ?;
430450
431451 // Create test headers starting from checkpoint height
432- let checkpoint_height = 1_100_000 ;
433- let headers = BlockHeader :: dummy_batch ( checkpoint_height..checkpoint_height + 100 ) ;
434-
435- let mut base_state = ChainState :: new ( ) ;
436- base_state. sync_base_height = checkpoint_height;
437- storage. store_chain_state ( & base_state) . await ?;
438-
439- storage. store_headers_at_height ( & headers, checkpoint_height) . await ?;
440- assert_eq ! ( storage. get_stored_headers_len( ) . await , headers. len( ) as u32 ) ;
441-
442- // Verify headers are stored at correct blockchain heights
443- let header_at_base = storage. get_header ( checkpoint_height) . await ?;
444- assert_eq ! (
445- header_at_base. expect( "Header at base blockchain height should exist" ) ,
446- headers[ 0 ]
447- ) ;
448-
449- let header_at_ending = storage. get_header ( checkpoint_height + 99 ) . await ?;
450- assert_eq ! (
451- header_at_ending. expect( "Header at ending blockchain height should exist" ) ,
452- headers[ 99 ]
453- ) ;
454-
455- // Test the reverse index (hash -> blockchain height)
456- let hash_0 = headers[ 0 ] . block_hash ( ) ;
457- let height_0 = storage. get_header_height_by_hash ( & hash_0) . await ?;
458- assert_eq ! (
459- height_0,
460- Some ( checkpoint_height) ,
461- "Hash should map to blockchain height 1,100,000"
462- ) ;
463-
464- let hash_99 = headers[ 99 ] . block_hash ( ) ;
465- let height_99 = storage. get_header_height_by_hash ( & hash_99) . await ?;
466- assert_eq ! (
467- height_99,
468- Some ( checkpoint_height + 99 ) ,
469- "Hash should map to blockchain height 1,100,099"
470- ) ;
471-
472- // Store chain state to persist sync_base_height
473- let mut chain_state = ChainState :: new ( ) ;
474- chain_state. sync_base_height = checkpoint_height;
475- storage. store_chain_state ( & chain_state) . await ?;
476-
477- // Force save to disk
478- storage. persist ( ) . await ;
452+ const CHECKPOINT_HEIGHT : u32 = 1_100_000 ;
453+ let headers: Vec < BlockHeader > = BlockHeader :: dummy_batch ( 0 ..100 ) ;
454+
455+ storage. store_headers_at_height ( & headers, CHECKPOINT_HEIGHT ) . await ?;
456+
457+ check_storage ( & storage, & headers) . await ?;
479458
459+ storage. shutdown ( ) . await ;
480460 drop ( storage) ;
481461
482- // Create a new storage instance to test index rebuilding
483- let storage2 = DiskStorageManager :: new ( temp_dir. path ( ) . to_path_buf ( ) ) . await ?;
484-
485- // Verify the index was rebuilt correctly
486- let height_after_rebuild = storage2. get_header_height_by_hash ( & hash_0) . await ?;
487- assert_eq ! (
488- height_after_rebuild,
489- Some ( checkpoint_height) ,
490- "After index rebuild, hash should still map to blockchain height 1,100,000"
491- ) ;
492-
493- // Verify header can still be retrieved by blockchain height after reload
494- let header_after_reload = storage2. get_header ( checkpoint_height) . await ?;
495- assert ! (
496- header_after_reload. is_some( ) ,
497- "Header at base blockchain height should exist after reload"
498- ) ;
499- assert_eq ! ( header_after_reload. unwrap( ) , headers[ 0 ] ) ;
462+ let storage = DiskStorageManager :: new ( temp_dir. path ( ) ) . await ?;
500463
501- Ok ( ( ) )
464+ check_storage ( & storage, & headers) . await ?;
465+
466+ return Ok ( ( ) ) ;
467+
468+ async fn check_storage (
469+ storage : & DiskStorageManager ,
470+ headers : & [ BlockHeader ] ,
471+ ) -> StorageResult < ( ) > {
472+ assert_eq ! ( storage. get_stored_headers_len( ) . await , headers. len( ) as u32 ) ;
473+
474+ let header_at_base = storage. get_header ( CHECKPOINT_HEIGHT ) . await ?;
475+ assert_eq ! ( header_at_base, Some ( headers[ 0 ] ) ) ;
476+
477+ let header_at_ending = storage. get_header ( CHECKPOINT_HEIGHT + 99 ) . await ?;
478+ assert_eq ! ( header_at_ending, Some ( headers[ 99 ] ) ) ;
479+
480+ // Test the reverse index (hash -> blockchain height)
481+ let hash_0 = headers[ 0 ] . block_hash ( ) ;
482+ let height_0 = storage. get_header_height_by_hash ( & hash_0) . await ?;
483+ assert_eq ! (
484+ height_0,
485+ Some ( CHECKPOINT_HEIGHT ) ,
486+ "Hash should map to blockchain height 1,100,000"
487+ ) ;
488+
489+ let hash_99 = headers[ 99 ] . block_hash ( ) ;
490+ let height_99 = storage. get_header_height_by_hash ( & hash_99) . await ?;
491+ assert_eq ! (
492+ height_99,
493+ Some ( CHECKPOINT_HEIGHT + 99 ) ,
494+ "Hash should map to blockchain height 1,100,099"
495+ ) ;
496+
497+ Ok ( ( ) )
498+ }
502499 }
503500
504501 #[ tokio:: test]
505- async fn test_shutdown_flushes_index ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
506- let temp_dir = TempDir :: new ( ) ?;
507- let base_path = temp_dir. path ( ) . to_path_buf ( ) ;
508- let headers = BlockHeader :: dummy_batch ( 0 ..11_000 ) ;
509- let last_hash = headers. last ( ) . unwrap ( ) . block_hash ( ) ;
502+ async fn test_reverse_index_disk_storage ( ) {
503+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
510504
511505 {
512- let mut storage = DiskStorageManager :: new ( base_path. clone ( ) ) . await ?;
506+ let mut storage = DiskStorageManager :: new ( temp_dir. path ( ) ) . await . unwrap ( ) ;
507+
508+ // Create and store headers
509+ let headers = BlockHeader :: dummy_batch ( 0 ..10 ) ;
513510
514- storage. store_headers ( & headers[ ..10_000 ] ) . await ?;
515- storage. persist ( ) . await ;
511+ storage. store_headers ( & headers) . await . unwrap ( ) ;
512+
513+ // Test reverse lookups
514+ for ( i, header) in headers. iter ( ) . enumerate ( ) {
515+ let hash = header. block_hash ( ) ;
516+ let height = storage. get_header_height_by_hash ( & hash) . await . unwrap ( ) ;
517+ assert_eq ! ( height, Some ( i as u32 ) , "Height mismatch for header {}" , i) ;
518+ }
516519
517- storage. store_headers ( & headers[ 10_000 ..] ) . await ?;
518520 storage. shutdown ( ) . await ;
519521 }
520522
521- let storage = DiskStorageManager :: new ( base_path) . await ?;
522- let height = storage. get_header_height_by_hash ( & last_hash) . await ?;
523- assert_eq ! ( height, Some ( 10_999 ) ) ;
523+ // Test persistence - reload storage and verify index still works
524+ {
525+ let storage = DiskStorageManager :: new ( & temp_dir. path ( ) ) . await . unwrap ( ) ;
526+
527+ // The index should have been rebuilt from the loaded headers
528+ // We need to get the actual headers that were stored to test properly
529+ for i in 0 ..10 {
530+ let stored_header = storage. get_header ( i) . await . unwrap ( ) . unwrap ( ) ;
531+ let hash = stored_header. block_hash ( ) ;
532+ let height = storage. get_header_height_by_hash ( & hash) . await . unwrap ( ) ;
533+ assert_eq ! ( height, Some ( i) , "Height mismatch after reload for header {}" , i) ;
534+ }
535+ }
536+ }
524537
525- Ok ( ( ) )
538+ #[ tokio:: test]
539+ async fn test_clear_clears_index ( ) {
540+ let mut storage =
541+ DiskStorageManager :: with_temp_dir ( ) . await . expect ( "Failed to create tmp storage" ) ;
542+
543+ // Store some headers
544+ let header = BlockHeader :: dummy_batch ( 0 ..1 ) ;
545+ storage. store_headers ( & header) . await . unwrap ( ) ;
546+
547+ let hash = header[ 0 ] . block_hash ( ) ;
548+ assert ! ( storage. get_header_height_by_hash( & hash) . await . unwrap( ) . is_some( ) ) ;
549+
550+ // Clear storage
551+ storage. clear ( ) . await . unwrap ( ) ;
552+
553+ // Verify index is cleared
554+ assert ! ( storage. get_header_height_by_hash( & hash) . await . unwrap( ) . is_none( ) ) ;
555+ }
556+
557+ #[ tokio:: test]
558+ async fn test_lock_lifecycle ( ) {
559+ let temp_dir = TempDir :: new ( ) . expect ( "Failed to create temp directory" ) ;
560+ let path = temp_dir. path ( ) . to_path_buf ( ) ;
561+ let lock_path = {
562+ let mut lock_file = path. clone ( ) ;
563+ lock_file. set_extension ( "lock" ) ;
564+ lock_file
565+ } ;
566+
567+ let mut storage1 = DiskStorageManager :: new ( & path) . await . unwrap ( ) ;
568+ assert ! ( lock_path. exists( ) , "Lock file should exist while storage is open" ) ;
569+ storage1. clear ( ) . await . expect ( "Failed to clear the storage" ) ;
570+ assert ! ( lock_path. exists( ) , "Lock file should exist after storage is cleared" ) ;
571+
572+ let storage2 = DiskStorageManager :: new ( & path) . await ;
573+ assert ! ( storage2. is_err( ) , "Second storage manager should fail" ) ;
574+
575+ // Lock file removed when storage drops
576+ drop ( storage1) ;
577+ assert ! ( !lock_path. exists( ) , "Lock file should be removed after storage drops" ) ;
578+
579+ // Can reopen storage after previous one dropped
580+ let storage3 = DiskStorageManager :: new ( & path) . await ;
581+ assert ! ( storage3. is_ok( ) , "Should reopen after previous storage dropped" ) ;
526582 }
527583}
0 commit comments