Skip to main content

linera_views/views/
log_view.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::BTreeMap,
6    ops::{Bound, Range, RangeBounds},
7};
8
9use allocative::Allocative;
10#[cfg(with_metrics)]
11use linera_base::prometheus_util::MeasureLatency as _;
12use serde::{de::DeserializeOwned, Serialize};
13
14use crate::{
15    batch::Batch,
16    common::{from_bytes_option_or_default, HasherOutput},
17    context::Context,
18    hashable_wrapper::WrappedHashableContainerView,
19    historical_hash_wrapper::HistoricallyHashableView,
20    store::ReadableKeyValueStore as _,
21    views::{ClonableView, HashableView, Hasher, View, ViewError, MIN_VIEW_TAG},
22};
23
24#[cfg(with_metrics)]
25mod metrics {
26    use std::sync::LazyLock;
27
28    use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
29    use prometheus::HistogramVec;
30
31    /// The runtime of hash computation
32    pub static LOG_VIEW_HASH_RUNTIME: LazyLock<HistogramVec> = LazyLock::new(|| {
33        register_histogram_vec(
34            "log_view_hash_runtime",
35            "LogView hash runtime",
36            &[],
37            exponential_bucket_latencies(5.0),
38        )
39    });
40}
41
42/// Key tags to create the sub-keys of a `LogView` on top of the base key.
43#[repr(u8)]
44enum KeyTag {
45    /// Prefix for the storing of the variable `stored_count`.
46    Count = MIN_VIEW_TAG,
47    /// Prefix for the indices of the log.
48    Index,
49}
50
51/// A view that supports logging values of type `T`.
52#[derive(Debug, Allocative)]
53#[allocative(bound = "C, T: Allocative")]
54pub struct LogView<C, T> {
55    /// The view context.
56    #[allocative(skip)]
57    context: C,
58    /// Whether to clear storage before applying updates.
59    delete_storage_first: bool,
60    /// The number of entries persisted in storage.
61    stored_count: usize,
62    /// New values not yet persisted to storage.
63    new_values: Vec<T>,
64}
65
66impl<C, T> View for LogView<C, T>
67where
68    C: Context,
69    T: Send + Sync + Serialize,
70{
71    const NUM_INIT_KEYS: usize = 1;
72
73    type Context = C;
74
75    fn context(&self) -> &C {
76        &self.context
77    }
78
79    fn pre_load(context: &C) -> Result<Vec<Vec<u8>>, ViewError> {
80        Ok(vec![context.base_key().base_tag(KeyTag::Count as u8)])
81    }
82
83    fn post_load(context: C, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
84        let stored_count =
85            from_bytes_option_or_default(values.first().ok_or(ViewError::PostLoadValuesError)?)?;
86        Ok(Self {
87            context,
88            delete_storage_first: false,
89            stored_count,
90            new_values: Vec::new(),
91        })
92    }
93
94    fn rollback(&mut self) {
95        self.delete_storage_first = false;
96        self.new_values.clear();
97    }
98
99    async fn has_pending_changes(&self) -> bool {
100        if self.delete_storage_first {
101            return true;
102        }
103        !self.new_values.is_empty()
104    }
105
106    fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
107        let mut delete_view = false;
108        if self.delete_storage_first {
109            batch.delete_key_prefix(self.context.base_key().bytes.clone());
110            delete_view = true;
111        }
112        if !self.new_values.is_empty() {
113            delete_view = false;
114            for (count, value) in (self.stored_count..).zip(&self.new_values) {
115                let key = self
116                    .context
117                    .base_key()
118                    .derive_tag_key(KeyTag::Index as u8, &count)?;
119                batch.put_key_value(key, value)?;
120            }
121            let count = self.stored_count + self.new_values.len();
122            let key = self.context.base_key().base_tag(KeyTag::Count as u8);
123            batch.put_key_value(key, &count)?;
124        }
125        Ok(delete_view)
126    }
127
128    fn post_save(&mut self) {
129        if self.delete_storage_first {
130            self.stored_count = 0;
131        }
132        self.stored_count += self.new_values.len();
133        self.new_values.clear();
134        self.delete_storage_first = false;
135    }
136
137    fn clear(&mut self) {
138        self.delete_storage_first = true;
139        self.new_values.clear();
140    }
141}
142
143impl<C, T> ClonableView for LogView<C, T>
144where
145    C: Context,
146    T: Clone + Send + Sync + Serialize,
147{
148    fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
149        Ok(LogView {
150            context: self.context.clone(),
151            delete_storage_first: self.delete_storage_first,
152            stored_count: self.stored_count,
153            new_values: self.new_values.clone(),
154        })
155    }
156}
157
158impl<C, T> LogView<C, T>
159where
160    C: Context,
161{
162    /// Pushes a value to the end of the log.
163    /// ```rust
164    /// # tokio_test::block_on(async {
165    /// # use linera_views::context::MemoryContext;
166    /// # use linera_views::log_view::LogView;
167    /// # use linera_views::views::View;
168    /// # let context = MemoryContext::new_for_testing(());
169    /// let mut log = LogView::load(context).await.unwrap();
170    /// log.push(34);
171    /// # })
172    /// ```
173    pub fn push(&mut self, value: T) {
174        self.new_values.push(value);
175    }
176
177    /// Reads the size of the log.
178    /// ```rust
179    /// # tokio_test::block_on(async {
180    /// # use linera_views::context::MemoryContext;
181    /// # use linera_views::log_view::LogView;
182    /// # use linera_views::views::View;
183    /// # let context = MemoryContext::new_for_testing(());
184    /// let mut log = LogView::load(context).await.unwrap();
185    /// log.push(34);
186    /// log.push(42);
187    /// assert_eq!(log.count(), 2);
188    /// # })
189    /// ```
190    pub fn count(&self) -> usize {
191        if self.delete_storage_first {
192            self.new_values.len()
193        } else {
194            self.stored_count + self.new_values.len()
195        }
196    }
197
198    /// Obtains the extra data.
199    pub fn extra(&self) -> &C::Extra {
200        self.context.extra()
201    }
202}
203
204impl<C, T> LogView<C, T>
205where
206    C: Context,
207    T: Clone + DeserializeOwned + Serialize + Send + Sync,
208{
209    /// Reads the logged value with the given index (including staged ones).
210    /// ```rust
211    /// # tokio_test::block_on(async {
212    /// # use linera_views::context::MemoryContext;
213    /// # use linera_views::log_view::LogView;
214    /// # use linera_views::views::View;
215    /// # let context = MemoryContext::new_for_testing(());
216    /// let mut log = LogView::load(context).await.unwrap();
217    /// log.push(34);
218    /// assert_eq!(log.get(0).await.unwrap(), Some(34));
219    /// # })
220    /// ```
221    pub async fn get(&self, index: usize) -> Result<Option<T>, ViewError> {
222        let value = if self.delete_storage_first {
223            self.new_values.get(index).cloned()
224        } else if index < self.stored_count {
225            let key = self
226                .context
227                .base_key()
228                .derive_tag_key(KeyTag::Index as u8, &index)?;
229            self.context.store().read_value(&key).await?
230        } else {
231            self.new_values.get(index - self.stored_count).cloned()
232        };
233        Ok(value)
234    }
235
236    /// Reads several logged keys (including staged ones)
237    /// ```rust
238    /// # tokio_test::block_on(async {
239    /// # use linera_views::context::MemoryContext;
240    /// # use linera_views::log_view::LogView;
241    /// # use linera_views::views::View;
242    /// # let context = MemoryContext::new_for_testing(());
243    /// let mut log = LogView::load(context).await.unwrap();
244    /// log.push(34);
245    /// log.push(42);
246    /// assert_eq!(
247    ///     log.multi_get(vec![0, 1]).await.unwrap(),
248    ///     vec![Some(34), Some(42)]
249    /// );
250    /// # })
251    /// ```
252    pub async fn multi_get(&self, indices: Vec<usize>) -> Result<Vec<Option<T>>, ViewError> {
253        let mut result = Vec::new();
254        if self.delete_storage_first {
255            for index in indices {
256                result.push(self.new_values.get(index).cloned());
257            }
258        } else {
259            let mut index_to_positions = BTreeMap::<usize, Vec<usize>>::new();
260            for (pos, index) in indices.into_iter().enumerate() {
261                if index < self.stored_count {
262                    index_to_positions.entry(index).or_default().push(pos);
263                    result.push(None);
264                } else {
265                    result.push(self.new_values.get(index - self.stored_count).cloned());
266                }
267            }
268            let mut keys = Vec::new();
269            let mut vec_positions = Vec::new();
270            for (index, positions) in index_to_positions {
271                let key = self
272                    .context
273                    .base_key()
274                    .derive_tag_key(KeyTag::Index as u8, &index)?;
275                keys.push(key);
276                vec_positions.push(positions);
277            }
278            let values = self.context.store().read_multi_values(&keys).await?;
279            for (positions, value) in vec_positions.into_iter().zip(values) {
280                if let Some((&last, rest)) = positions.split_last() {
281                    for &position in rest {
282                        *result.get_mut(position).unwrap() = value.clone();
283                    }
284                    *result.get_mut(last).unwrap() = value;
285                }
286            }
287        }
288        Ok(result)
289    }
290
291    async fn read_context(&self, range: Range<usize>) -> Result<Vec<T>, ViewError> {
292        let count = range.len();
293        let mut keys = Vec::with_capacity(count);
294        for index in range {
295            let key = self
296                .context
297                .base_key()
298                .derive_tag_key(KeyTag::Index as u8, &index)?;
299            keys.push(key);
300        }
301        let mut values = Vec::with_capacity(count);
302        for entry in self.context.store().read_multi_values(&keys).await? {
303            match entry {
304                None => {
305                    return Err(ViewError::MissingEntries("LogView".into()));
306                }
307                Some(value) => values.push(value),
308            }
309        }
310        Ok(values)
311    }
312
313    /// Reads the logged values in the given range (including staged ones).
314    /// ```rust
315    /// # tokio_test::block_on(async {
316    /// # use linera_views::context::MemoryContext;
317    /// # use linera_views::log_view::LogView;
318    /// # use linera_views::views::View;
319    /// # let context = MemoryContext::new_for_testing(());
320    /// let mut log = LogView::load(context).await.unwrap();
321    /// log.push(34);
322    /// log.push(42);
323    /// log.push(56);
324    /// assert_eq!(log.read(0..2).await.unwrap(), vec![34, 42]);
325    /// # })
326    /// ```
327    pub async fn read<R>(&self, range: R) -> Result<Vec<T>, ViewError>
328    where
329        R: RangeBounds<usize>,
330    {
331        let effective_stored_count = if self.delete_storage_first {
332            0
333        } else {
334            self.stored_count
335        };
336        let end = match range.end_bound() {
337            Bound::Included(end) => *end + 1,
338            Bound::Excluded(end) => *end,
339            Bound::Unbounded => self.count(),
340        }
341        .min(self.count());
342        let start = match range.start_bound() {
343            Bound::Included(start) => *start,
344            Bound::Excluded(start) => *start + 1,
345            Bound::Unbounded => 0,
346        };
347        if start >= end {
348            return Ok(Vec::new());
349        }
350        if start < effective_stored_count {
351            if end <= effective_stored_count {
352                self.read_context(start..end).await
353            } else {
354                let mut values = self.read_context(start..effective_stored_count).await?;
355                values.extend(
356                    self.new_values[0..(end - effective_stored_count)]
357                        .iter()
358                        .cloned(),
359                );
360                Ok(values)
361            }
362        } else {
363            Ok(
364                self.new_values[(start - effective_stored_count)..(end - effective_stored_count)]
365                    .to_vec(),
366            )
367        }
368    }
369}
370
371impl<C, T> HashableView for LogView<C, T>
372where
373    C: Context,
374    T: Send + Sync + Clone + Serialize + DeserializeOwned,
375{
376    type Hasher = sha3::Sha3_256;
377
378    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
379        self.hash().await
380    }
381
382    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
383        #[cfg(with_metrics)]
384        let _hash_latency = metrics::LOG_VIEW_HASH_RUNTIME.measure_latency();
385        let elements = self.read(..).await?;
386        let mut hasher = sha3::Sha3_256::default();
387        hasher.update_with_bcs_bytes(&elements)?;
388        Ok(hasher.finalize())
389    }
390}
391
392/// Type wrapping `LogView` while memoizing the hash.
393pub type HashedLogView<C, T> = WrappedHashableContainerView<C, LogView<C, T>, HasherOutput>;
394
395/// Wrapper around `LogView` to compute hashes based on the history of changes.
396pub type HistoricallyHashedLogView<C, T> = HistoricallyHashableView<C, LogView<C, T>>;
397
398#[cfg(not(web))]
399mod graphql {
400    use std::borrow::Cow;
401
402    use super::LogView;
403    use crate::{
404        context::Context,
405        graphql::{hash_name, mangle},
406    };
407
408    impl<C: Send + Sync, T: async_graphql::OutputType> async_graphql::TypeName for LogView<C, T> {
409        fn type_name() -> Cow<'static, str> {
410            format!(
411                "LogView_{}_{:08x}",
412                mangle(T::type_name()),
413                hash_name::<T>()
414            )
415            .into()
416        }
417    }
418
419    #[async_graphql::Object(cache_control(no_cache), name_type)]
420    impl<C: Context, T: async_graphql::OutputType> LogView<C, T>
421    where
422        T: serde::ser::Serialize + serde::de::DeserializeOwned + Clone + Send + Sync,
423    {
424        #[graphql(derived(name = "count"))]
425        async fn count_(&self) -> Result<u32, async_graphql::Error> {
426            Ok(self.count() as u32)
427        }
428
429        async fn entries(
430            &self,
431            start: Option<usize>,
432            end: Option<usize>,
433        ) -> async_graphql::Result<Vec<T>> {
434            Ok(self
435                .read(start.unwrap_or_default()..end.unwrap_or_else(|| self.count()))
436                .await?)
437        }
438    }
439}