Skip to content

Commit 900f6b3

Browse files
Address a bug in try_load_entries of CollectionView. (#4753)
## Motivation In `CollectionView`, we have `KeyTag::Index` / `KeyTag::Subview`, but they were wrongly assigned in the function `try_load_entries`. ## Proposal The correction was straightforward, once it was found. ## Test Plan A random test has been added. This test concerns both the `CollectionView` and `ReentrantCollectionView`. This test covers both the found bug, the preceding one with `W::NUM_INIT_KEYS == 0`. Therefore, the two added tests in the `view_test.rs` are removed. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links A test case was created in [linera_end_to_end_tests/graphql_induced_crash](https://github.com/MathieuDutSik/linera_end_to_end_tests). It could be put as an additional integration test that exercises the GraphQL capabilities of Linera.
1 parent ca867f6 commit 900f6b3

File tree

3 files changed

+131
-54
lines changed

3 files changed

+131
-54
lines changed

linera-views/src/views/collection_view.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,11 +367,15 @@ impl<W: View> ByteCollectionView<W::Context, W> {
367367
None => {
368368
results.push(None); // Placeholder, may be updated later
369369
if !self.delete_storage_first {
370+
let key = self
371+
.context
372+
.base_key()
373+
.base_tag_index(KeyTag::Subview as u8, &short_key);
374+
let subview_context = self.context.clone_with_base_key(key);
370375
let key = self
371376
.context
372377
.base_key()
373378
.base_tag_index(KeyTag::Index as u8, &short_key);
374-
let subview_context = self.context.clone_with_base_key(key.clone());
375379
keys_to_check.push(key);
376380
keys_to_check_metadata.push((position, subview_context));
377381
}

linera-views/tests/random_container_tests.rs

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ use std::collections::{BTreeMap, BTreeSet};
66
use anyhow::Result;
77
use 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
};
1919
use 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)]
512631
pub struct QueueStateView<C> {
513632
pub queue: HashedQueueView<C, u8>,

linera-views/tests/views_tests.rs

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ use linera_views::{
1515
Batch, WriteOperation,
1616
WriteOperation::{Delete, DeletePrefix, Put},
1717
},
18-
collection_view::{CollectionView, HashedCollectionView},
18+
collection_view::HashedCollectionView,
1919
context::{Context, MemoryContext, ViewContext},
2020
key_value_store_view::{KeyValueStoreView, ViewContainer},
2121
log_view::HashedLogView,
2222
lru_caching::LruCachingMemoryDatabase,
23-
map_view::{ByteMapView, HashedMapView, MapView},
23+
map_view::{ByteMapView, HashedMapView},
2424
memory::MemoryDatabase,
2525
queue_view::HashedQueueView,
2626
random::make_deterministic_rng,
27-
reentrant_collection_view::{HashedReentrantCollectionView, ReentrantCollectionView},
27+
reentrant_collection_view::HashedReentrantCollectionView,
2828
register_view::HashedRegisterView,
2929
set_view::HashedSetView,
3030
store::{KeyValueDatabase, TestKeyValueDatabase as _, WritableKeyValueStore as _},
@@ -614,52 +614,6 @@ where
614614
.await
615615
}
616616

617-
#[derive(RootView)]
618-
pub struct NestedCollectionMapView<C> {
619-
pub map: CollectionView<C, String, MapView<C, String, u64>>,
620-
}
621-
622-
// This test exercise the case W::NUM_INIT_KEYS == 0
623-
// in CollectionView.
624-
#[tokio::test]
625-
async fn test_nested_collection_map_view() -> anyhow::Result<()> {
626-
let context = MemoryContext::new_for_testing(());
627-
{
628-
let mut view = NestedCollectionMapView::load(context.clone()).await?;
629-
let subview = view.map.load_entry_mut("Bonjour").await?;
630-
subview.insert("A bientot", 49)?;
631-
view.save().await?;
632-
}
633-
let view = NestedCollectionMapView::load(context).await?;
634-
let keys = vec!["Bonjour".to_string()];
635-
let subviews = view.map.try_load_entries(&keys).await?;
636-
assert!(subviews[0].is_some());
637-
Ok(())
638-
}
639-
640-
#[derive(RootView)]
641-
pub struct NestedReentrantCollectionMapView<C> {
642-
pub map: ReentrantCollectionView<C, String, MapView<C, String, u64>>,
643-
}
644-
645-
#[tokio::test]
646-
async fn test_nested_reentrant_collection_map_view() -> anyhow::Result<()> {
647-
let context = MemoryContext::new_for_testing(());
648-
{
649-
let mut view = NestedReentrantCollectionMapView::load(context.clone()).await?;
650-
{
651-
let mut subview = view.map.try_load_entry_mut("Bonjour").await?;
652-
subview.insert("A bientot", 49)?;
653-
}
654-
view.save().await?;
655-
}
656-
let view = NestedReentrantCollectionMapView::load(context).await?;
657-
let keys = vec!["Bonjour".to_string()];
658-
let subviews = view.map.try_load_entries(&keys).await?;
659-
assert!(subviews[0].is_some());
660-
Ok(())
661-
}
662-
663617
#[derive(CryptoHashRootView)]
664618
pub struct ByteMapStateView<C> {
665619
pub map: ByteMapView<C, u8>,

0 commit comments

Comments
 (0)