Skip to main content

linera_views/views/
hashable_wrapper.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7    sync::Mutex,
8};
9
10use allocative::Allocative;
11use linera_base::visit_allocative_simple;
12use serde::{de::DeserializeOwned, Serialize};
13
14use crate::{
15    batch::Batch,
16    common::from_bytes_option,
17    context::Context,
18    views::{ClonableView, HashableView, Hasher, ReplaceContext, View, ViewError, MIN_VIEW_TAG},
19};
20
21/// Wrapping a view to memoize its hash.
22#[derive(Debug, Allocative)]
23#[allocative(bound = "C, O, W: Allocative")]
24pub struct WrappedHashableContainerView<C, W, O> {
25    /// Phantom data for the context type.
26    #[allocative(skip)]
27    _phantom: PhantomData<C>,
28    /// The hash persisted in storage.
29    #[allocative(visit = visit_allocative_simple)]
30    stored_hash: Option<O>,
31    /// Memoized hash, if any.
32    #[allocative(visit = visit_allocative_simple)]
33    hash: Mutex<Option<O>>,
34    /// The wrapped view.
35    inner: W,
36}
37
38/// Key tags to create the sub-keys of a `WrappedHashableContainerView` on top of the base key.
39#[repr(u8)]
40enum KeyTag {
41    /// Prefix for the indices of the view.
42    Inner = MIN_VIEW_TAG,
43    /// Prefix for the hash.
44    Hash,
45}
46
47impl<C, W, O, C2> ReplaceContext<C2> for WrappedHashableContainerView<C, W, O>
48where
49    W: HashableView<Hasher: Hasher<Output = O>, Context = C> + ReplaceContext<C2>,
50    <W as ReplaceContext<C2>>::Target: HashableView<Hasher: Hasher<Output = O>>,
51    O: Serialize + DeserializeOwned + Send + Sync + Copy + PartialEq,
52    C: Context,
53    C2: Context,
54{
55    type Target = WrappedHashableContainerView<C2, <W as ReplaceContext<C2>>::Target, O>;
56
57    async fn with_context(
58        &mut self,
59        ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
60    ) -> Self::Target {
61        let hash = *self.hash.lock().unwrap();
62        WrappedHashableContainerView {
63            _phantom: PhantomData,
64            stored_hash: self.stored_hash,
65            hash: Mutex::new(hash),
66            inner: self.inner.with_context(ctx).await,
67        }
68    }
69}
70
71impl<W: HashableView, O> View for WrappedHashableContainerView<W::Context, W, O>
72where
73    W: HashableView<Hasher: Hasher<Output = O>>,
74    O: Serialize + DeserializeOwned + Send + Sync + Copy + PartialEq,
75{
76    const NUM_INIT_KEYS: usize = 1 + W::NUM_INIT_KEYS;
77
78    type Context = W::Context;
79
80    fn context(&self) -> &Self::Context {
81        self.inner.context()
82    }
83
84    fn pre_load(context: &Self::Context) -> Result<Vec<Vec<u8>>, ViewError> {
85        let mut v = vec![context.base_key().base_tag(KeyTag::Hash as u8)];
86        let base_key = context.base_key().base_tag(KeyTag::Inner as u8);
87        let context = context.clone_with_base_key(base_key);
88        v.extend(W::pre_load(&context)?);
89        Ok(v)
90    }
91
92    fn post_load(context: Self::Context, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
93        let hash = from_bytes_option(values.first().ok_or(ViewError::PostLoadValuesError)?)?;
94        let base_key = context.base_key().base_tag(KeyTag::Inner as u8);
95        let context = context.clone_with_base_key(base_key);
96        let inner = W::post_load(
97            context,
98            values.get(1..).ok_or(ViewError::PostLoadValuesError)?,
99        )?;
100        Ok(Self {
101            _phantom: PhantomData,
102            stored_hash: hash,
103            hash: Mutex::new(hash),
104            inner,
105        })
106    }
107
108    fn rollback(&mut self) {
109        self.inner.rollback();
110        *self.hash.get_mut().unwrap() = self.stored_hash;
111    }
112
113    async fn has_pending_changes(&self) -> bool {
114        if self.inner.has_pending_changes().await {
115            return true;
116        }
117        let hash = self.hash.lock().unwrap();
118        self.stored_hash != *hash
119    }
120
121    fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
122        let delete_view = self.inner.pre_save(batch)?;
123        let hash = *self.hash.lock().unwrap();
124        if delete_view {
125            let mut key_prefix = self.inner.context().base_key().bytes.clone();
126            key_prefix.pop();
127            batch.delete_key_prefix(key_prefix);
128        } else if self.stored_hash != hash {
129            let mut key = self.inner.context().base_key().bytes.clone();
130            let tag = key.last_mut().unwrap();
131            *tag = KeyTag::Hash as u8;
132            match hash {
133                None => batch.delete_key(key),
134                Some(hash) => batch.put_key_value(key, &hash)?,
135            }
136        }
137        Ok(delete_view)
138    }
139
140    fn post_save(&mut self) {
141        self.inner.post_save();
142        let hash = *self.hash.get_mut().unwrap();
143        self.stored_hash = hash;
144    }
145
146    fn clear(&mut self) {
147        self.inner.clear();
148        *self.hash.get_mut().unwrap() = None;
149    }
150}
151
152impl<W, O> ClonableView for WrappedHashableContainerView<W::Context, W, O>
153where
154    W: HashableView + ClonableView,
155    O: Serialize + DeserializeOwned + Send + Sync + Copy + PartialEq,
156    W::Hasher: Hasher<Output = O>,
157{
158    fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
159        Ok(WrappedHashableContainerView {
160            _phantom: PhantomData,
161            stored_hash: self.stored_hash,
162            hash: Mutex::new(*self.hash.get_mut().unwrap()),
163            inner: self.inner.clone_unchecked()?,
164        })
165    }
166}
167
168impl<W, O> HashableView for WrappedHashableContainerView<W::Context, W, O>
169where
170    W: HashableView,
171    O: Serialize + DeserializeOwned + Send + Sync + Copy + PartialEq,
172    W::Hasher: Hasher<Output = O>,
173{
174    type Hasher = W::Hasher;
175
176    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
177        let hash = *self.hash.get_mut().unwrap();
178        match hash {
179            Some(hash) => Ok(hash),
180            None => {
181                let new_hash = self.inner.hash_mut().await?;
182                let hash = self.hash.get_mut().unwrap();
183                *hash = Some(new_hash);
184                Ok(new_hash)
185            }
186        }
187    }
188
189    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
190        let hash = *self.hash.lock().unwrap();
191        match hash {
192            Some(hash) => Ok(hash),
193            None => {
194                let new_hash = self.inner.hash().await?;
195                let mut hash = self.hash.lock().unwrap();
196                *hash = Some(new_hash);
197                Ok(new_hash)
198            }
199        }
200    }
201}
202
203impl<C, W, O> Deref for WrappedHashableContainerView<C, W, O> {
204    type Target = W;
205
206    fn deref(&self) -> &W {
207        &self.inner
208    }
209}
210
211impl<C, W, O> DerefMut for WrappedHashableContainerView<C, W, O> {
212    fn deref_mut(&mut self) -> &mut W {
213        *self.hash.get_mut().unwrap() = None;
214        &mut self.inner
215    }
216}
217
218impl<C, W, O> WrappedHashableContainerView<C, W, O> {
219    /// Returns a mutable reference to the wrapped view without invalidating
220    /// the memoized hash. Only safe to use for operations that do not alter
221    /// the logical content (e.g. dropping an in-memory cache).
222    pub(crate) fn inner_mut_preserve_hash(&mut self) -> &mut W {
223        &mut self.inner
224    }
225}
226
227#[cfg(with_graphql)]
228mod graphql {
229    use std::borrow::Cow;
230
231    use super::WrappedHashableContainerView;
232    use crate::context::Context;
233
234    impl<C, W, O> async_graphql::OutputType for WrappedHashableContainerView<C, W, O>
235    where
236        C: Context,
237        W: async_graphql::OutputType + Send + Sync,
238        O: Send + Sync,
239    {
240        fn type_name() -> Cow<'static, str> {
241            W::type_name()
242        }
243
244        fn qualified_type_name() -> String {
245            W::qualified_type_name()
246        }
247
248        fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
249            W::create_type_info(registry)
250        }
251
252        async fn resolve(
253            &self,
254            ctx: &async_graphql::ContextSelectionSet<'_>,
255            field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
256        ) -> async_graphql::ServerResult<async_graphql::Value> {
257            (**self).resolve(ctx, field).await
258        }
259    }
260}