@@ -6,15 +6,15 @@ use std::collections::{BTreeMap, BTreeSet};
66use anyhow:: Result ;
77use linera_views:: {
88 bucket_queue_view:: HashedBucketQueueView ,
9- collection_view:: HashedCollectionView ,
9+ collection_view:: { CollectionView , HashedCollectionView } ,
1010 context:: { Context , MemoryContext } ,
1111 key_value_store_view:: { KeyValueStoreView , SizeData } ,
12- map_view:: HashedByteMapView ,
12+ map_view:: { HashedByteMapView , MapView } ,
1313 queue_view:: HashedQueueView ,
1414 random:: make_deterministic_rng,
15- reentrant_collection_view:: HashedReentrantCollectionView ,
15+ reentrant_collection_view:: { HashedReentrantCollectionView , ReentrantCollectionView } ,
1616 register_view:: RegisterView ,
17- views:: { CryptoHashRootView , CryptoHashView , RootView , View } ,
17+ views:: { CryptoHashRootView , CryptoHashView , HashableView as _ , RootView , View } ,
1818} ;
1919use rand:: { distributions:: Uniform , Rng , RngCore } ;
2020
@@ -508,6 +508,125 @@ async fn bucket_queue_view_mutability_check() -> Result<()> {
508508 Ok ( ( ) )
509509}
510510
511+ #[ derive( CryptoHashRootView ) ]
512+ pub struct NestedCollectionMapView < C > {
513+ pub map1 : CollectionView < C , String , MapView < C , String , u64 > > ,
514+ pub map2 : ReentrantCollectionView < C , String , MapView < C , String , u64 > > ,
515+ }
516+
517+ impl < C : Context > NestedCollectionMapView < C > {
518+ async fn read_maps_nested_collection_map_view (
519+ & self ,
520+ ) -> Result < BTreeMap < String , BTreeMap < String , u64 > > > {
521+ let indices1 = self . map1 . indices ( ) . await ?;
522+ let indices2 = self . map2 . indices ( ) . await ?;
523+ assert_eq ! ( indices1, indices2, "Different set of indices" ) ;
524+
525+ let subviews1 = self . map1 . try_load_entries ( & indices1) . await ?;
526+ let subviews2 = self . map2 . try_load_entries ( & indices1) . await ?;
527+ let mut state_map = BTreeMap :: new ( ) ;
528+ for ( ( subview1, subview2) , index) in subviews1. into_iter ( ) . zip ( subviews2) . zip ( indices1) {
529+ let key_values1 = subview1. unwrap ( ) . index_values ( ) . await ?;
530+ let key_values2 = subview2. unwrap ( ) . index_values ( ) . await ?;
531+ assert_eq ! ( key_values1, key_values2, "key-values should be equal" ) ;
532+ let key_values = key_values1. into_iter ( ) . collect :: < BTreeMap < String , u64 > > ( ) ;
533+ state_map. insert ( index, key_values) ;
534+ }
535+ Ok ( state_map)
536+ }
537+ }
538+
539+ #[ tokio:: test]
540+ async fn nested_collection_map_view_check ( ) -> Result < ( ) > {
541+ let context = MemoryContext :: new_for_testing ( ( ) ) ;
542+ let mut rng = make_deterministic_rng ( ) ;
543+ let mut state_map: BTreeMap < String , BTreeMap < String , u64 > > = BTreeMap :: new ( ) ;
544+ let n = 20 ;
545+ for _ in 0 ..n {
546+ let mut view = NestedCollectionMapView :: load ( context. clone ( ) ) . await ?;
547+ let hash = view. crypto_hash ( ) . await ?;
548+ let save = rng. gen :: < bool > ( ) ;
549+
550+ let count_oper = rng. gen_range ( 0 ..25 ) ;
551+ let mut new_state_map = state_map. clone ( ) ;
552+ for _ in 0 ..count_oper {
553+ let keys: Vec < String > = new_state_map. keys ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ;
554+ let count = new_state_map. len ( ) ;
555+ let choice = rng. gen_range ( 0 ..5 ) ;
556+ if choice >= 2 {
557+ let key1 = rng. gen_range :: < u8 , _ > ( 0 ..10 ) ;
558+ let key1 = format ! ( "key1_{key1}" ) ;
559+ let key2 = rng. gen_range :: < u8 , _ > ( 0 ..10 ) ;
560+ let key2 = format ! ( "key2_{key2}" ) ;
561+ let value = rng. gen_range :: < u64 , _ > ( 0 ..100 ) ;
562+ // insert into maps.
563+ let subview1 = view. map1 . load_entry_mut ( & key1) . await ?;
564+ subview1. insert ( & key2, value) ?;
565+ let mut subview2 = view. map2 . try_load_entry_mut ( & key1) . await ?;
566+ subview2. insert ( & key2, value) ?;
567+ // insert into control
568+ let mut map = new_state_map. get ( & key1) . cloned ( ) . unwrap_or_default ( ) ;
569+ map. insert ( key2, value) ;
570+ new_state_map. insert ( key1, map) ;
571+ }
572+ if choice == 1 && count > 0 {
573+ let pos = rng. gen_range ( 0 ..count) as usize ;
574+ let key = keys[ pos] . clone ( ) ;
575+ view. map1 . remove_entry ( & key) ?;
576+ view. map2 . remove_entry ( & key) ?;
577+ new_state_map. remove ( & key) ;
578+ }
579+ if choice == 2 && count > 0 {
580+ let pos = rng. gen_range ( 0 ..count) ;
581+ let key1 = keys[ pos] . clone ( ) ;
582+ let submap = new_state_map. get_mut ( & key1) . unwrap ( ) ;
583+ let count = submap. len ( ) ;
584+ if count > 0 {
585+ let subkeys = submap
586+ . iter ( )
587+ . map ( |( key, _) | key. clone ( ) )
588+ . collect :: < Vec < _ > > ( ) ;
589+ let pos = rng. gen_range ( 0 ..count) ;
590+ let key2 = subkeys[ pos] . clone ( ) ;
591+ submap. remove ( & key2) ;
592+ // Removing some entries from the view
593+ let subview1 = view. map1 . load_entry_mut ( & key1) . await ?;
594+ subview1. remove ( & key2) ?;
595+ let mut subview2 = view. map2 . try_load_entry_mut ( & key1) . await ?;
596+ subview2. remove ( & key2) ?;
597+ }
598+ }
599+ let state_view = view. read_maps_nested_collection_map_view ( ) . await ?;
600+ assert_eq ! (
601+ state_view, new_state_map,
602+ "state_view should match new_state_map"
603+ ) ;
604+ let new_hash = view. crypto_hash ( ) . await ?;
605+ if state_map == new_state_map {
606+ assert_eq ! ( new_hash, hash) ;
607+ } else {
608+ // If equal it is a bug or a hash collision (unlikely)
609+ assert_ne ! ( new_hash, hash) ;
610+ }
611+ let hash1 = view. map1 . hash ( ) . await ?;
612+ let hash2 = view. map2 . hash ( ) . await ?;
613+ assert_eq ! (
614+ hash1, hash2,
615+ "hash for CollectionView / ReentrantCollectionView should match"
616+ ) ;
617+ }
618+ if save {
619+ if state_map != new_state_map {
620+ assert ! ( view. has_pending_changes( ) . await ) ;
621+ }
622+ state_map. clone_from ( & new_state_map) ;
623+ view. save ( ) . await ?;
624+ assert ! ( !view. has_pending_changes( ) . await ) ;
625+ }
626+ }
627+ Ok ( ( ) )
628+ }
629+
511630#[ derive( CryptoHashRootView ) ]
512631pub struct QueueStateView < C > {
513632 pub queue : HashedQueueView < C , u8 > ,
0 commit comments