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