1use std::{
5 marker::PhantomData,
6 ops::{Deref, DerefMut},
7 sync::Mutex,
8};
9
10use allocative::Allocative;
11#[cfg(with_metrics)]
12use linera_base::prometheus_util::MeasureLatency as _;
13use linera_base::visit_allocative_simple;
14
15use crate::{
16 batch::Batch,
17 common::from_bytes_option,
18 context::Context,
19 store::{ReadableKeyValueStore as _, WritableKeyValueStore as _},
20 views::{ClonableView, Hasher, HasherOutput, ReplaceContext, View, ViewError, MIN_VIEW_TAG},
21};
22
23#[cfg(with_metrics)]
24pub(crate) mod metrics {
25 use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
26 use prometheus::HistogramVec;
27
28 linera_base::declare_metrics! {
29 pub static HISTORICALLY_HASHABLE_VIEW_HASH_RUNTIME: HistogramVec =
31 register_histogram_vec(
32 "historically_hashable_view_hash_runtime",
33 "HistoricallyHashableView hash runtime",
34 &[],
35 exponential_bucket_latencies(5.0),
36 );
37 }
38}
39
40#[derive(Debug, Allocative)]
42#[allocative(bound = "C, W: Allocative")]
43pub struct HistoricallyHashableView<C, W> {
44 #[allocative(visit = visit_allocative_simple)]
46 stored_hash: Option<HasherOutput>,
47 inner: W,
49 #[allocative(visit = visit_allocative_simple)]
51 hash: Mutex<Option<HasherOutput>>,
52 #[allocative(visit = visit_allocative_simple)]
57 force_stored_hash: Option<HasherOutput>,
58 #[allocative(skip)]
60 _phantom: PhantomData<C>,
61}
62
63#[repr(u8)]
65enum KeyTag {
66 Inner = MIN_VIEW_TAG,
68 Hash,
70}
71
72impl<C, W> HistoricallyHashableView<C, W> {
73 fn make_hash(
74 stored_hash: Option<HasherOutput>,
75 batch: &Batch,
76 ) -> Result<HasherOutput, ViewError> {
77 #[cfg(with_metrics)]
78 let _hash_latency = metrics::HISTORICALLY_HASHABLE_VIEW_HASH_RUNTIME.measure_latency();
79 let stored_hash = stored_hash.unwrap_or_default();
80 if batch.is_empty() {
81 return Ok(stored_hash);
82 }
83 let mut hasher = sha3::Sha3_256::default();
84 hasher.update_with_bytes(&stored_hash)?;
85 hasher.update_with_bcs_bytes(&batch)?;
86 Ok(hasher.finalize())
87 }
88}
89
90impl<C, W, C2> ReplaceContext<C2> for HistoricallyHashableView<C, W>
91where
92 W: View<Context = C> + ReplaceContext<C2>,
93 C: Context,
94 C2: Context,
95{
96 type Target = HistoricallyHashableView<C2, <W as ReplaceContext<C2>>::Target>;
97
98 async fn with_context(
99 &mut self,
100 ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
101 ) -> Self::Target {
102 HistoricallyHashableView {
103 _phantom: PhantomData,
104 stored_hash: self.stored_hash,
105 hash: Mutex::new(*self.hash.get_mut().unwrap()),
106 force_stored_hash: self.force_stored_hash,
107 inner: self.inner.with_context(ctx).await,
108 }
109 }
110}
111
112impl<W> View for HistoricallyHashableView<W::Context, W>
113where
114 W: View,
115{
116 const NUM_INIT_KEYS: usize = 1 + W::NUM_INIT_KEYS;
117
118 type Context = W::Context;
119
120 fn context(&self) -> Self::Context {
121 self.inner.context().clone_with_trimmed_key(1)
123 }
124
125 fn pre_load(context: &Self::Context) -> Result<Vec<Vec<u8>>, ViewError> {
126 let mut v = vec![context.base_key().base_tag(KeyTag::Hash as u8)];
127 let base_key = context.base_key().base_tag(KeyTag::Inner as u8);
128 let context = context.clone_with_base_key(base_key);
129 v.extend(W::pre_load(&context)?);
130 Ok(v)
131 }
132
133 fn post_load(context: Self::Context, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
134 let hash = from_bytes_option(values.first().ok_or(ViewError::PostLoadValuesError)?)?;
135 let base_key = context.base_key().base_tag(KeyTag::Inner as u8);
136 let context = context.clone_with_base_key(base_key);
137 let inner = W::post_load(
138 context,
139 values.get(1..).ok_or(ViewError::PostLoadValuesError)?,
140 )?;
141 Ok(Self {
142 _phantom: PhantomData,
143 stored_hash: hash,
144 hash: Mutex::new(hash),
145 force_stored_hash: None,
146 inner,
147 })
148 }
149
150 async fn load(context: Self::Context) -> Result<Self, ViewError> {
151 let keys = Self::pre_load(&context)?;
152 let values = context.store().read_multi_values_bytes(&keys).await?;
153 Self::post_load(context, &values)
154 }
155
156 fn rollback(&mut self) {
157 self.inner.rollback();
158 *self.hash.get_mut().unwrap() = self.stored_hash;
159 self.force_stored_hash = None;
160 }
161
162 async fn has_pending_changes(&self) -> bool {
163 self.force_stored_hash.is_some() || self.inner.has_pending_changes().await
164 }
165
166 fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
167 let mut inner_batch = Batch::new();
168 self.inner.pre_save(&mut inner_batch)?;
169 let new_hash = {
170 let mut maybe_hash = self.hash.lock().unwrap();
171 if let Some(forced) = self.force_stored_hash {
172 *maybe_hash = Some(forced);
175 forced
176 } else {
177 match maybe_hash.as_mut() {
178 Some(hash) => *hash,
179 None => {
180 let hash = Self::make_hash(self.stored_hash, &inner_batch)?;
181 *maybe_hash = Some(hash);
182 hash
183 }
184 }
185 }
186 };
187 batch.operations.extend(inner_batch.operations);
188
189 if self.stored_hash != Some(new_hash) {
190 let mut key = self.inner.context().base_key().bytes.clone();
191 let tag = key.last_mut().unwrap();
192 *tag = KeyTag::Hash as u8;
193 batch.put_key_value(key, &new_hash)?;
194 }
195 Ok(false)
197 }
198
199 fn post_save(&mut self) {
200 let new_hash = self
201 .hash
202 .get_mut()
203 .unwrap()
204 .expect("hash should be computed in pre_save");
205 self.stored_hash = Some(new_hash);
206 self.force_stored_hash = None;
207 self.inner.post_save();
208 }
209
210 fn clear(&mut self) {
211 self.inner.clear();
212 *self.hash.get_mut().unwrap() = None;
213 self.force_stored_hash = None;
214 }
215}
216
217impl<W> ClonableView for HistoricallyHashableView<W::Context, W>
218where
219 W: ClonableView,
220{
221 fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
222 Ok(HistoricallyHashableView {
223 _phantom: PhantomData,
224 stored_hash: self.stored_hash,
225 hash: Mutex::new(*self.hash.get_mut().unwrap()),
226 force_stored_hash: self.force_stored_hash,
227 inner: self.inner.clone_unchecked()?,
228 })
229 }
230}
231
232impl<W: View> HistoricallyHashableView<W::Context, W> {
233 pub async fn historical_hash(&mut self) -> Result<HasherOutput, ViewError> {
235 if let Some(forced) = self.force_stored_hash {
236 return Ok(forced);
237 }
238 if let Some(hash) = self.hash.get_mut().unwrap() {
239 return Ok(*hash);
240 }
241 let mut batch = Batch::new();
242 self.inner.pre_save(&mut batch)?;
243 let hash = Self::make_hash(self.stored_hash, &batch)?;
244 *self.hash.get_mut().unwrap() = Some(hash);
246 Ok(hash)
247 }
248
249 pub async fn dump_content(&mut self) -> Result<(Vec<u8>, HasherOutput), ViewError> {
262 if self.inner.has_pending_changes().await {
263 return Err(ViewError::HasPendingChanges);
264 }
265 let context = self.inner.context();
266 let inner_prefix = context.base_key().bytes.clone();
270 let key_values = context
271 .store()
272 .find_key_values_by_prefix(&inner_prefix)
273 .await
274 .map_err(|err| ViewError::StoreError {
275 backend: "HistoricallyHashableView::dump_content",
276 error: Box::new(err),
277 must_reload_view: false,
278 })?;
279 let bytes = bcs::to_bytes(&key_values)?;
280 let hash = hash_bytes(&bytes);
281 self.force_stored_hash = Some(hash);
284 *self.hash.get_mut().unwrap() = None;
285 Ok((bytes, hash))
286 }
287
288 pub async fn restore_from_content(&mut self, bytes: &[u8]) -> Result<HasherOutput, ViewError> {
299 let entries = decode_key_values(bytes)?;
300 let hash = hash_bytes(bytes);
301
302 let context = self.inner.context();
303 let inner_base = context.base_key().bytes.clone();
304 let mut wrapper_hash_key = inner_base.clone();
305 *wrapper_hash_key
308 .last_mut()
309 .expect("inner base key is non-empty") = KeyTag::Hash as u8;
310
311 let mut batch = Batch::new();
312 batch.delete_key_prefix(inner_base.clone());
314 for (key, value) in entries {
316 let mut full_key = inner_base.clone();
317 full_key.extend_from_slice(&key);
318 batch.put_key_value_bytes(full_key, value);
319 }
320 batch.put_key_value(wrapper_hash_key, &hash)?;
322
323 context
324 .store()
325 .write_batch(batch)
326 .await
327 .map_err(|err| ViewError::StoreError {
328 backend: "HistoricallyHashableView::restore_from_content",
329 error: Box::new(err),
330 must_reload_view: false,
331 })?;
332
333 self.stored_hash = Some(hash);
336 *self.hash.get_mut().unwrap() = Some(hash);
337 self.force_stored_hash = None;
338
339 Ok(hash)
340 }
341}
342
343#[expect(clippy::type_complexity)]
353fn decode_key_values(bytes: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ViewError> {
354 let entries: Vec<(Vec<u8>, Vec<u8>)> = bcs::from_bytes(bytes)?;
355 for window in entries.windows(2) {
356 if window[1].0 <= window[0].0 {
357 return Err(ViewError::MalformedContent(
358 "keys must be in strictly increasing order",
359 ));
360 }
361 }
362 Ok(entries)
363}
364
365fn hash_bytes(bytes: &[u8]) -> HasherOutput {
366 const DOMAIN_TAG: &[u8] = b"linera-views::HistoricallyHashableView::dump_content/v1";
371 const _: () = assert!(DOMAIN_TAG.len() >= 32);
372 let mut hasher = sha3::Sha3_256::default();
373 hasher
374 .update_with_bytes(DOMAIN_TAG)
375 .expect("Sha3_256 hashing of a byte slice cannot fail");
376 hasher
377 .update_with_bytes(bytes)
378 .expect("Sha3_256 hashing of a byte slice cannot fail");
379 hasher.finalize()
380}
381
382impl<C, W> Deref for HistoricallyHashableView<C, W> {
383 type Target = W;
384
385 fn deref(&self) -> &W {
386 &self.inner
387 }
388}
389
390impl<C, W> DerefMut for HistoricallyHashableView<C, W> {
391 fn deref_mut(&mut self) -> &mut W {
392 *self.hash.get_mut().unwrap() = None;
394 &mut self.inner
395 }
396}
397
398#[cfg(with_graphql)]
399mod graphql {
400 use std::borrow::Cow;
401
402 use super::HistoricallyHashableView;
403 use crate::context::Context;
404
405 impl<C, W> async_graphql::OutputType for HistoricallyHashableView<C, W>
406 where
407 C: Context,
408 W: async_graphql::OutputType + Send + Sync,
409 {
410 fn type_name() -> Cow<'static, str> {
411 W::type_name()
412 }
413
414 fn qualified_type_name() -> String {
415 W::qualified_type_name()
416 }
417
418 fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
419 W::create_type_info(registry)
420 }
421
422 async fn resolve(
423 &self,
424 ctx: &async_graphql::ContextSelectionSet<'_>,
425 field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
426 ) -> async_graphql::ServerResult<async_graphql::Value> {
427 self.inner.resolve(ctx, field).await
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::{context::MemoryContext, register_view::RegisterView};
436
437 #[tokio::test]
438 async fn test_historically_hashable_view_initial_state() -> Result<(), ViewError> {
439 let context = MemoryContext::new_for_testing(());
440 let mut view =
441 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
442
443 assert!(!view.has_pending_changes().await);
445
446 let hash = view.historical_hash().await?;
448 assert_eq!(hash, HasherOutput::default());
449
450 Ok(())
451 }
452
453 #[tokio::test]
454 async fn test_historically_hashable_view_hash_changes_with_modifications(
455 ) -> Result<(), ViewError> {
456 let context = MemoryContext::new_for_testing(());
457 let mut view =
458 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
459
460 let hash0 = view.historical_hash().await?;
462
463 view.set(42);
465 assert!(view.has_pending_changes().await);
466
467 let hash1 = view.historical_hash().await?;
469
470 assert!(view.has_pending_changes().await);
472 assert_ne!(hash0, hash1);
473
474 let mut batch = Batch::new();
476 view.pre_save(&mut batch)?;
477 context.store().write_batch(batch).await?;
478 view.post_save();
479 assert!(!view.has_pending_changes().await);
480 assert_eq!(hash1, view.historical_hash().await?);
481
482 view.set(84);
484 let hash2 = view.historical_hash().await?;
485 assert_ne!(hash1, hash2);
486
487 Ok(())
488 }
489
490 #[tokio::test]
491 async fn test_historically_hashable_view_reloaded() -> Result<(), ViewError> {
492 let context = MemoryContext::new_for_testing(());
493 let mut view =
494 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
495
496 view.set(42);
498 let mut batch = Batch::new();
499 view.pre_save(&mut batch)?;
500 context.store().write_batch(batch).await?;
501 view.post_save();
502
503 let hash_after_flush = view.historical_hash().await?;
504
505 let mut view2 =
507 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
508
509 let hash_reloaded = view2.historical_hash().await?;
511 assert_eq!(hash_after_flush, hash_reloaded);
512
513 Ok(())
514 }
515
516 #[tokio::test]
517 async fn test_historically_hashable_view_rollback() -> Result<(), ViewError> {
518 let context = MemoryContext::new_for_testing(());
519 let mut view =
520 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
521
522 view.set(42);
524 let mut batch = Batch::new();
525 view.pre_save(&mut batch)?;
526 context.store().write_batch(batch).await?;
527 view.post_save();
528
529 let hash_before = view.historical_hash().await?;
530 assert!(!view.has_pending_changes().await);
531
532 view.set(84);
534 assert!(view.has_pending_changes().await);
535 let hash_modified = view.historical_hash().await?;
536 assert_ne!(hash_before, hash_modified);
537
538 view.rollback();
540 assert!(!view.has_pending_changes().await);
541
542 let hash_after_rollback = view.historical_hash().await?;
544 assert_eq!(hash_before, hash_after_rollback);
545
546 Ok(())
547 }
548
549 #[tokio::test]
550 async fn test_historically_hashable_view_clear() -> Result<(), ViewError> {
551 let context = MemoryContext::new_for_testing(());
552 let mut view =
553 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
554
555 view.set(42);
557 let mut batch = Batch::new();
558 view.pre_save(&mut batch)?;
559 context.store().write_batch(batch).await?;
560 view.post_save();
561
562 assert_ne!(view.historical_hash().await?, HasherOutput::default());
563
564 view.clear();
566 assert!(view.has_pending_changes().await);
567
568 let mut batch = Batch::new();
570 let delete_view = view.pre_save(&mut batch)?;
571 assert!(!delete_view);
572 context.store().write_batch(batch).await?;
573 view.post_save();
574
575 assert_ne!(view.historical_hash().await?, HasherOutput::default());
577
578 Ok(())
579 }
580
581 #[tokio::test]
582 async fn test_historically_hashable_view_clone_unchecked() -> Result<(), ViewError> {
583 let context = MemoryContext::new_for_testing(());
584 let mut view =
585 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
586
587 view.set(42);
589 let mut batch = Batch::new();
590 view.pre_save(&mut batch)?;
591 context.store().write_batch(batch).await?;
592 view.post_save();
593
594 let original_hash = view.historical_hash().await?;
595
596 let mut cloned_view = view.clone_unchecked()?;
598
599 let cloned_hash = cloned_view.historical_hash().await?;
601 assert_eq!(original_hash, cloned_hash);
602
603 cloned_view.set(84);
605 let cloned_hash_after = cloned_view.historical_hash().await?;
606 assert_ne!(original_hash, cloned_hash_after);
607
608 let original_hash_after = view.historical_hash().await?;
610 assert_eq!(original_hash, original_hash_after);
611
612 Ok(())
613 }
614
615 #[tokio::test]
616 async fn test_historically_hashable_view_flush_updates_stored_hash() -> Result<(), ViewError> {
617 let context = MemoryContext::new_for_testing(());
618 let mut view =
619 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
620
621 assert!(!view.has_pending_changes().await);
623
624 view.set(42);
626 assert!(view.has_pending_changes().await);
627
628 let hash_before_flush = view.historical_hash().await?;
629
630 let mut batch = Batch::new();
632 let delete_view = view.pre_save(&mut batch)?;
633 assert!(!delete_view);
634 context.store().write_batch(batch).await?;
635 view.post_save();
636
637 assert!(!view.has_pending_changes().await);
638
639 view.set(84);
641 let hash_after_second_change = view.historical_hash().await?;
642
643 assert_ne!(hash_before_flush, hash_after_second_change);
645
646 Ok(())
647 }
648
649 #[tokio::test]
650 async fn test_historically_hashable_view_deref() -> Result<(), ViewError> {
651 let context = MemoryContext::new_for_testing(());
652 let mut view =
653 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
654
655 view.set(42);
657 assert_eq!(*view.get(), 42);
658
659 view.set(84);
661 assert_eq!(*view.get(), 84);
662
663 Ok(())
664 }
665
666 #[tokio::test]
667 async fn test_historically_hashable_view_sequential_modifications() -> Result<(), ViewError> {
668 async fn get_hash(values: &[u32]) -> Result<HasherOutput, ViewError> {
669 let context = MemoryContext::new_for_testing(());
670 let mut view =
671 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
672
673 let mut previous_hash = view.historical_hash().await?;
674 for &value in values {
675 view.set(value);
676 if value % 2 == 0 {
677 let mut batch = Batch::new();
679 view.pre_save(&mut batch)?;
680 context.store().write_batch(batch).await?;
681 view.post_save();
682 }
683 let current_hash = view.historical_hash().await?;
684 assert_ne!(previous_hash, current_hash);
685 previous_hash = current_hash;
686 }
687 Ok(previous_hash)
688 }
689
690 let h1 = get_hash(&[10, 20, 30, 40, 50]).await?;
691 let h2 = get_hash(&[20, 30, 40, 50]).await?;
692 let h3 = get_hash(&[20, 21, 30, 40, 50]).await?;
693 assert_ne!(h1, h2);
694 assert_eq!(h2, h3);
695 Ok(())
696 }
697
698 #[tokio::test]
699 async fn test_historically_hashable_view_flush_with_no_hash_change() -> Result<(), ViewError> {
700 let context = MemoryContext::new_for_testing(());
701 let mut view =
702 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
703
704 view.set(42);
706 let mut batch = Batch::new();
707 view.pre_save(&mut batch)?;
708 context.store().write_batch(batch).await?;
709 view.post_save();
710
711 let hash_before = view.historical_hash().await?;
712
713 let mut batch = Batch::new();
715 view.pre_save(&mut batch)?;
716 assert!(batch.is_empty());
717 context.store().write_batch(batch).await?;
718 view.post_save();
719
720 let hash_after = view.historical_hash().await?;
721 assert_eq!(hash_before, hash_after);
722
723 Ok(())
724 }
725
726 #[tokio::test]
727 async fn test_dump_content_then_save_records_content_hash() -> Result<(), ViewError> {
728 let context = MemoryContext::new_for_testing(());
732 let mut view =
733 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
734
735 view.set(42);
736 let mut batch = Batch::new();
737 view.pre_save(&mut batch)?;
738 context.store().write_batch(batch).await?;
739 view.post_save();
740
741 let history_hash_before = view.historical_hash().await?;
742
743 let (bytes, content_hash) = view.dump_content().await?;
744 assert_ne!(history_hash_before, content_hash);
745 assert_eq!(view.historical_hash().await?, content_hash);
746
747 let mut batch = Batch::new();
749 view.pre_save(&mut batch)?;
750 context.store().write_batch(batch).await?;
751 view.post_save();
752
753 let mut reloaded =
755 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
756 assert_eq!(reloaded.historical_hash().await?, content_hash);
757 assert_eq!(*reloaded.get(), 42);
758
759 let (bytes_again, content_hash_again) = reloaded.dump_content().await?;
761 assert_eq!(bytes, bytes_again);
762 assert_eq!(content_hash, content_hash_again);
763
764 Ok(())
765 }
766
767 #[tokio::test]
768 async fn test_restore_then_reload_matches_source() -> Result<(), ViewError> {
769 let source_context = MemoryContext::new_for_testing(());
772 let mut source =
773 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(source_context.clone())
774 .await?;
775 source.set(7);
776 let mut batch = Batch::new();
777 source.pre_save(&mut batch)?;
778 source_context.store().write_batch(batch).await?;
779 source.post_save();
780 let (bytes, expected_hash) = source.dump_content().await?;
781
782 let target_context = MemoryContext::new_for_testing(());
784 let mut target =
785 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(target_context.clone())
786 .await?;
787 let restored_hash = target.restore_from_content(&bytes).await?;
788 assert_eq!(restored_hash, expected_hash);
789
790 let mut reloaded =
792 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(target_context.clone())
793 .await?;
794 assert_eq!(reloaded.historical_hash().await?, expected_hash);
795 assert_eq!(*reloaded.get(), 7);
796
797 let (bytes_after_restore, _) = reloaded.dump_content().await?;
799 assert_eq!(bytes, bytes_after_restore);
800
801 Ok(())
802 }
803
804 #[tokio::test]
805 async fn test_dump_content_errors_on_pending_changes() -> Result<(), ViewError> {
806 let context = MemoryContext::new_for_testing(());
807 let mut view =
808 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
809 view.set(1);
810 match view.dump_content().await {
812 Err(ViewError::HasPendingChanges) => Ok(()),
813 other => panic!("expected HasPendingChanges, got {other:?}"),
814 }
815 }
816
817 #[tokio::test]
818 async fn test_dump_content_rollback_discards_override() -> Result<(), ViewError> {
819 let context = MemoryContext::new_for_testing(());
822 let mut view =
823 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
824 view.set(99);
825 let mut batch = Batch::new();
826 view.pre_save(&mut batch)?;
827 context.store().write_batch(batch).await?;
828 view.post_save();
829
830 let history_hash = view.historical_hash().await?;
831 let (_, content_hash) = view.dump_content().await?;
832 assert_eq!(view.historical_hash().await?, content_hash);
833
834 view.rollback();
835 assert_eq!(view.historical_hash().await?, history_hash);
836
837 Ok(())
838 }
839
840 #[tokio::test]
841 async fn test_decode_rejects_unsorted_keys() -> Result<(), ViewError> {
842 let entries: Vec<(Vec<u8>, Vec<u8>)> = vec![
844 (b"b".to_vec(), b"v1".to_vec()),
845 (b"a".to_vec(), b"v2".to_vec()),
846 ];
847 let bytes = bcs::to_bytes(&entries).expect("encoding cannot fail");
848 let context = MemoryContext::new_for_testing(());
849 let mut view =
850 HistoricallyHashableView::<_, RegisterView<_, u32>>::load(context.clone()).await?;
851 match view.restore_from_content(&bytes).await {
852 Err(ViewError::MalformedContent(_)) => Ok(()),
853 other => panic!("expected MalformedContent, got {other:?}"),
854 }
855 }
856}