linera_views/views/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{fmt::Debug, io::Write};
5
6use async_trait::async_trait;
7use linera_base::{
8    crypto::CryptoHash,
9    data_types::ArithmeticError,
10    identifiers::{BlobId, EventId},
11};
12pub use linera_views_derive::{
13    ClonableView, CryptoHashRootView, CryptoHashView, HashableView, RootView, View,
14};
15use serde::Serialize;
16use thiserror::Error;
17
18use crate::{batch::Batch, common::HasherOutput};
19
20#[cfg(test)]
21#[path = "unit_tests/views.rs"]
22mod tests;
23
24/// The `RegisterView` implements a register for a single value.
25pub mod register_view;
26
27/// The `LogView` implements a log list that can be pushed.
28pub mod log_view;
29
30/// The `BucketQueueView` implements a queue that can push on the back and delete on the front and group data in buckets.
31pub mod bucket_queue_view;
32
33/// The `QueueView` implements a queue that can push on the back and delete on the front.
34pub mod queue_view;
35
36/// The `MapView` implements a map with ordered keys.
37pub mod map_view;
38
39/// The `SetView` implements a set with ordered entries.
40pub mod set_view;
41
42/// The `CollectionView` implements a map structure whose keys are ordered and the values are views.
43pub mod collection_view;
44
45/// The `ReentrantCollectionView` implements a map structure whose keys are ordered and the values are views with concurrent access.
46pub mod reentrant_collection_view;
47
48/// The implementation of a key-value store view.
49pub mod key_value_store_view;
50
51/// Wrapping a view to compute a hash.
52pub mod hashable_wrapper;
53
54/// The minimum value for the view tags. Values in `0..MIN_VIEW_TAG` are used for other purposes.
55pub const MIN_VIEW_TAG: u8 = 1;
56
57/// A view gives exclusive access to read and write the data stored at an underlying
58/// address in storage.
59#[async_trait]
60pub trait View<C>: Sized {
61    /// The number of keys used for the initialization
62    const NUM_INIT_KEYS: usize;
63
64    /// Obtains a mutable reference to the internal context.
65    fn context(&self) -> &C;
66
67    /// Creates the keys needed for loading the view
68    fn pre_load(context: &C) -> Result<Vec<Vec<u8>>, ViewError>;
69
70    /// Loads a view from the values
71    fn post_load(context: C, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError>;
72
73    /// Loads a view
74    async fn load(context: C) -> Result<Self, ViewError>;
75
76    /// Discards all pending changes. After that `flush` should have no effect to storage.
77    fn rollback(&mut self);
78
79    /// Returns [`true`] if flushing this view would result in changes to the persistent storage.
80    async fn has_pending_changes(&self) -> bool;
81
82    /// Clears the view. That can be seen as resetting to default. If the clear is followed
83    /// by a flush then all the relevant data is removed on the storage.
84    fn clear(&mut self);
85
86    /// Persists changes to storage. This leaves the view still usable and is essentially neutral to the
87    /// program running. Crash-resistant storage implementations are expected to accumulate the desired
88    /// changes in the `batch` variable first. If the view is dropped without calling `flush`, staged
89    /// changes are simply lost.
90    /// The returned boolean indicates whether the operation removes the view or not.
91    fn flush(&mut self, batch: &mut Batch) -> Result<bool, ViewError>;
92
93    /// Builds a trivial view that is already deleted
94    fn new(context: C) -> Result<Self, ViewError> {
95        let values = vec![None; Self::NUM_INIT_KEYS];
96        let mut view = Self::post_load(context, &values)?;
97        view.clear();
98        Ok(view)
99    }
100}
101
102/// Main error type for the crate.
103#[derive(Error, Debug)]
104pub enum ViewError {
105    /// BCS serialization error.
106    #[error(transparent)]
107    BcsError(#[from] bcs::Error),
108
109    /// We failed to acquire an entry in a `CollectionView` or a `ReentrantCollectionView`.
110    #[error("trying to access a collection view or reentrant collection view while some entries are still being accessed")]
111    CannotAcquireCollectionEntry,
112
113    /// Input output error.
114    #[error("I/O error")]
115    IoError(#[from] std::io::Error),
116
117    /// Arithmetic error
118    #[error(transparent)]
119    ArithmeticError(#[from] ArithmeticError),
120
121    /// An error happened while trying to lock.
122    #[error("Failed to lock collection entry: {0:?}")]
123    TryLockError(Vec<u8>),
124
125    /// Tokio errors can happen while joining.
126    #[error("Panic in sub-task: {0}")]
127    TokioJoinError(#[from] tokio::task::JoinError),
128
129    /// Errors within the context can occur and are presented as `ViewError`.
130    #[error("Storage operation error in {backend}: {error}")]
131    StoreError {
132        /// backend can be e.g. RocksDB / DynamoDB / Memory / etc.
133        backend: String,
134        /// error is the specific problem that occurred within that context
135        error: String,
136    },
137
138    /// The key must not be too long
139    #[error("The key must not be too long")]
140    KeyTooLong,
141
142    /// The entry does not exist in memory
143    // FIXME(#148): This belongs to a future `linera_storage::StoreError`.
144    #[error("Entry does not exist in memory: {0}")]
145    NotFound(String),
146
147    /// The database is corrupt: Entries don't have the expected hash.
148    #[error("Inconsistent database entries")]
149    InconsistentEntries,
150
151    /// The database is corrupt: Some entries are missing
152    #[error("Missing database entries")]
153    MissingEntries,
154
155    /// The values are incoherent.
156    #[error("Post load values error")]
157    PostLoadValuesError,
158
159    /// The value is too large for the client
160    #[error("The value is too large for the client")]
161    TooLargeValue,
162
163    /// Some blobs were not found.
164    #[error("Blobs not found: {0:?}")]
165    BlobsNotFound(Vec<BlobId>),
166
167    /// Some events were not found.
168    #[error("Events not found: {0:?}")]
169    EventsNotFound(Vec<EventId>),
170}
171
172impl ViewError {
173    /// Creates a `NotFound` error with the given message and key.
174    pub fn not_found<T: Debug>(msg: &str, key: T) -> ViewError {
175        ViewError::NotFound(format!("{} {:?}", msg, key))
176    }
177}
178
179/// A view that supports hashing its values.
180#[async_trait]
181pub trait HashableView<C>: View<C> {
182    /// How to compute hashes.
183    type Hasher: Hasher;
184
185    /// Computes the hash of the values.
186    ///
187    /// Implementations do not need to include a type tag. However, the usual precautions
188    /// to enforce collision resistance must be applied (e.g. including the length of a
189    /// collection of values).
190    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError>;
191
192    /// Computes the hash of the values.
193    ///
194    /// Implementations do not need to include a type tag. However, the usual precautions
195    /// to enforce collision resistance must be applied (e.g. including the length of a
196    /// collection of values).
197    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError>;
198}
199
200/// The requirement for the hasher type in [`HashableView`].
201pub trait Hasher: Default + Write + Send + Sync + 'static {
202    /// The output type.
203    type Output: Debug + Clone + Eq + AsRef<[u8]> + 'static;
204
205    /// Finishes the hashing process and returns its output.
206    fn finalize(self) -> Self::Output;
207
208    /// Serializes a value with BCS and includes it in the hash.
209    fn update_with_bcs_bytes(&mut self, value: &impl Serialize) -> Result<(), ViewError> {
210        bcs::serialize_into(self, value)?;
211        Ok(())
212    }
213
214    /// Includes bytes in the hash.
215    fn update_with_bytes(&mut self, value: &[u8]) -> Result<(), ViewError> {
216        self.write_all(value)?;
217        Ok(())
218    }
219}
220
221impl Hasher for sha3::Sha3_256 {
222    type Output = HasherOutput;
223
224    fn finalize(self) -> Self::Output {
225        <sha3::Sha3_256 as sha3::Digest>::finalize(self)
226    }
227}
228
229/// A [`View`] whose staged modifications can be saved in storage.
230#[async_trait]
231pub trait RootView<C>: View<C> {
232    /// Saves the root view to the database context
233    async fn save(&mut self) -> Result<(), ViewError>;
234}
235
236/// A [`View`] that also supports crypto hash
237#[async_trait]
238pub trait CryptoHashView<C>: HashableView<C> {
239    /// Computing the hash and attributing the type to it.
240    async fn crypto_hash(&self) -> Result<CryptoHash, ViewError>;
241
242    /// Computing the hash and attributing the type to it.
243    async fn crypto_hash_mut(&mut self) -> Result<CryptoHash, ViewError>;
244}
245
246/// A [`RootView`] that also supports crypto hash
247#[async_trait]
248pub trait CryptoHashRootView<C>: RootView<C> + CryptoHashView<C> {}
249
250/// A [`ClonableView`] supports being shared (unsafely) by cloning it.
251///
252/// Sharing is unsafe because by having two view instances for the same data, they may have invalid
253/// state if both are used for writing.
254///
255/// Sharing the view is guaranteed to not cause data races if only one of the shared view instances
256/// is used for writing at any given point in time.
257pub trait ClonableView<C>: View<C> {
258    /// Creates a clone of this view, sharing the underlying storage context but prone to
259    /// data races which can corrupt the view state.
260    fn clone_unchecked(&mut self) -> Result<Self, ViewError>;
261}