linera_views/views/
register_view.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4#[cfg(with_metrics)]
5use linera_base::prometheus_util::MeasureLatency as _;
6use serde::{de::DeserializeOwned, Serialize};
7
8use crate::{
9    batch::Batch,
10    common::{from_bytes_option_or_default, HasherOutput},
11    context::Context,
12    hashable_wrapper::WrappedHashableContainerView,
13    store::ReadableKeyValueStore as _,
14    views::{ClonableView, HashableView, Hasher, View},
15    ViewError,
16};
17
18#[cfg(with_metrics)]
19mod metrics {
20    use std::sync::LazyLock;
21
22    use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
23    use prometheus::HistogramVec;
24
25    /// The runtime of hash computation
26    pub static REGISTER_VIEW_HASH_RUNTIME: LazyLock<HistogramVec> = LazyLock::new(|| {
27        register_histogram_vec(
28            "register_view_hash_runtime",
29            "RegisterView hash runtime",
30            &[],
31            exponential_bucket_latencies(5.0),
32        )
33    });
34}
35
36/// A view that supports modifying a single value of type `T`.
37#[derive(Debug)]
38pub struct RegisterView<C, T> {
39    delete_storage_first: bool,
40    context: C,
41    stored_value: Box<T>,
42    update: Option<Box<T>>,
43}
44
45impl<C, T> View for RegisterView<C, T>
46where
47    C: Context,
48    T: Default + Send + Sync + Serialize + DeserializeOwned,
49{
50    const NUM_INIT_KEYS: usize = 1;
51
52    type Context = C;
53
54    fn context(&self) -> &C {
55        &self.context
56    }
57
58    fn pre_load(context: &C) -> Result<Vec<Vec<u8>>, ViewError> {
59        Ok(vec![context.base_key().bytes.clone()])
60    }
61
62    fn post_load(context: C, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
63        let value =
64            from_bytes_option_or_default(values.first().ok_or(ViewError::PostLoadValuesError)?)?;
65        let stored_value = Box::new(value);
66        Ok(Self {
67            delete_storage_first: false,
68            context,
69            stored_value,
70            update: None,
71        })
72    }
73
74    async fn load(context: C) -> Result<Self, ViewError> {
75        let keys = Self::pre_load(&context)?;
76        let values = context.store().read_multi_values_bytes(keys).await?;
77        Self::post_load(context, &values)
78    }
79
80    fn rollback(&mut self) {
81        self.delete_storage_first = false;
82        self.update = None;
83    }
84
85    async fn has_pending_changes(&self) -> bool {
86        if self.delete_storage_first {
87            return true;
88        }
89        self.update.is_some()
90    }
91
92    fn flush(&mut self, batch: &mut Batch) -> Result<bool, ViewError> {
93        let mut delete_view = false;
94        if self.delete_storage_first {
95            batch.delete_key(self.context.base_key().bytes.clone());
96            self.stored_value = Box::default();
97            delete_view = true;
98        } else if let Some(value) = self.update.take() {
99            let key = self.context.base_key().bytes.clone();
100            batch.put_key_value(key, &value)?;
101            self.stored_value = value;
102        }
103        self.delete_storage_first = false;
104        self.update = None;
105        Ok(delete_view)
106    }
107
108    fn clear(&mut self) {
109        self.delete_storage_first = true;
110        self.update = Some(Box::default());
111    }
112}
113
114impl<C, T> ClonableView for RegisterView<C, T>
115where
116    C: Context,
117    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
118{
119    fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
120        Ok(RegisterView {
121            delete_storage_first: self.delete_storage_first,
122            context: self.context.clone(),
123            stored_value: self.stored_value.clone(),
124            update: self.update.clone(),
125        })
126    }
127}
128
129impl<C, T> RegisterView<C, T>
130where
131    C: Context,
132{
133    /// Access the current value in the register.
134    /// ```rust
135    /// # tokio_test::block_on(async {
136    /// # use linera_views::context::MemoryContext;
137    /// # use linera_views::register_view::RegisterView;
138    /// # use linera_views::views::View;
139    /// # let context = MemoryContext::new_for_testing(());
140    /// let mut register = RegisterView::<_, u32>::load(context).await.unwrap();
141    /// let value = register.get();
142    /// assert_eq!(*value, 0);
143    /// # })
144    /// ```
145    pub fn get(&self) -> &T {
146        match &self.update {
147            None => &self.stored_value,
148            Some(value) => value,
149        }
150    }
151
152    /// Sets the value in the register.
153    /// ```rust
154    /// # tokio_test::block_on(async {
155    /// # use linera_views::context::MemoryContext;
156    /// # use linera_views::register_view::RegisterView;
157    /// # use linera_views::views::View;
158    /// # let context = MemoryContext::new_for_testing(());
159    /// let mut register = RegisterView::load(context).await.unwrap();
160    /// register.set(5);
161    /// let value = register.get();
162    /// assert_eq!(*value, 5);
163    /// # })
164    /// ```
165    pub fn set(&mut self, value: T) {
166        self.delete_storage_first = false;
167        self.update = Some(Box::new(value));
168    }
169
170    /// Obtains the extra data.
171    pub fn extra(&self) -> &C::Extra {
172        self.context.extra()
173    }
174}
175
176impl<C, T> RegisterView<C, T>
177where
178    C: Context,
179    T: Clone + Serialize,
180{
181    /// Obtains a mutable reference to the value in the register.
182    /// ```rust
183    /// # tokio_test::block_on(async {
184    /// # use linera_views::context::MemoryContext;
185    /// # use linera_views::register_view::RegisterView;
186    /// # use linera_views::views::View;
187    /// # let context = MemoryContext::new_for_testing(());
188    /// let mut register: RegisterView<_, u32> = RegisterView::load(context).await.unwrap();
189    /// let value = register.get_mut();
190    /// assert_eq!(*value, 0);
191    /// # })
192    /// ```
193    pub fn get_mut(&mut self) -> &mut T {
194        self.delete_storage_first = false;
195        match &mut self.update {
196            Some(value) => value,
197            update => {
198                *update = Some(self.stored_value.clone());
199                update.as_mut().unwrap()
200            }
201        }
202    }
203
204    fn compute_hash(&self) -> Result<<sha3::Sha3_256 as Hasher>::Output, ViewError> {
205        #[cfg(with_metrics)]
206        let _hash_latency = metrics::REGISTER_VIEW_HASH_RUNTIME.measure_latency();
207        let mut hasher = sha3::Sha3_256::default();
208        hasher.update_with_bcs_bytes(self.get())?;
209        Ok(hasher.finalize())
210    }
211}
212
213impl<C, T> HashableView for RegisterView<C, T>
214where
215    C: Context,
216    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
217{
218    type Hasher = sha3::Sha3_256;
219
220    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
221        self.compute_hash()
222    }
223
224    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
225        self.compute_hash()
226    }
227}
228
229/// Type wrapping `RegisterView` while memoizing the hash.
230pub type HashedRegisterView<C, T> =
231    WrappedHashableContainerView<C, RegisterView<C, T>, HasherOutput>;
232
233#[cfg(with_graphql)]
234mod graphql {
235    use std::borrow::Cow;
236
237    use super::RegisterView;
238    use crate::context::Context;
239
240    impl<C, T> async_graphql::OutputType for RegisterView<C, T>
241    where
242        C: Context,
243        T: async_graphql::OutputType + Send + Sync,
244    {
245        fn type_name() -> Cow<'static, str> {
246            T::type_name()
247        }
248
249        fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
250            T::create_type_info(registry)
251        }
252
253        async fn resolve(
254            &self,
255            ctx: &async_graphql::ContextSelectionSet<'_>,
256            field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
257        ) -> async_graphql::ServerResult<async_graphql::Value> {
258            self.get().resolve(ctx, field).await
259        }
260    }
261}