willow_data_model/storage/
store.rs

1use core::{fmt::Debug, ops::RangeBounds};
2
3use anyhash::Hasher;
4use ufotofu::prelude::*;
5
6use crate::prelude::*;
7
8/// The most basic trait describing storage for [`Entry`](Entries).
9///
10/// A `Store` is a collection of entries such that none of them would [prefix-prune](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning).
11///
12/// This trait provides only a very basic set of operations: creation of new entries, querying individual entries and all entries within an [`Area`], local deletion, and accessing (subslices of) payloads. More specialised operations are provided by subtraits.
13///
14/// Stores must implement [`Clone`], and the expectation is that cloning is cheap and that changes on one clone also affect all other clones. You perform concurrent operations on a store by cloning it and then performing the operations on different clones.
15///
16/// Important: persistent stores are not required to persist mutations immediately. Call the [`Store::flush`] method to ensure that an operation is persisted.
17pub trait Store<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>: Clone {
18    /// The type of errors the store implementation might yield on any operation.
19    type InternalError;
20
21    /// Creates an [`AuthorisedEntry`] with the given data and payload, and atomically inserts the payload and entry into the store.
22    ///
23    /// If the new entry would be prefix-pruned immediately by a newer entry already in the store, this method returns `Ok(None)`. If the new entry prefix-prunes older entries in the store, those older entries are automatically removed.
24    ///
25    /// If the producer does not error, it must yield exactly `payload_length` many items and then the final `()` — otherwise, this method panics.
26    async fn create_entry<Data, P, H>(
27        &mut self,
28        data: &Data,
29        payload_producer: P,
30        payload_length: u64,
31        ingredients: &AT::Ingredients,
32    ) -> Result<
33        Option<AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>>,
34        CreateEntryError<Self::InternalError, P::Error, AT::CreationError>,
35    >
36    where
37        Data: ?Sized + Namespaced<N> + Coordinatelike<MCL, MCC, MPL, S>,
38        P: BulkProducer<Item = u8, Final = ()>,
39        H: Default + Hasher<PD>,
40        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD> + Debug,
41        N: Debug,
42        S: Debug,
43        PD: Debug;
44
45    /// Creates an [`Entry`] with the given data and payload, and atomically inserts the payload and entry into the store, unless it would prune one or more older entries.
46    ///
47    /// If the new entry would be prefix-pruned immediately by a newer entry already in the store, this method returns `Ok(None)`. If the new entry prefix-prunes older entries in the store, the store remains unchanged
48    ///
49    /// If the producer does not error, it must yield exactly `payload_length` many items and then the final `()` — otherwise, this method panics.
50    async fn create_entry_nondestructive<Data, P, H>(
51        &mut self,
52        data: &Data,
53        payload_producer: P,
54        payload_length: u64,
55        ingredients: &AT::Ingredients,
56    ) -> Result<
57        NondestructiveInsert<MCL, MCC, MPL, N, S, PD, AT>,
58        CreateEntryError<Self::InternalError, P::Error, AT::CreationError>,
59    >
60    where
61        Data: ?Sized + Namespaced<N> + Coordinatelike<MCL, MCC, MPL, S>,
62        P: BulkProducer<Item = u8, Final = ()>,
63        H: Default + Hasher<PD>,
64        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD> + Debug,
65        N: Debug,
66        S: Debug,
67        PD: Debug;
68
69    /// Inserts a given [`AuthorisedEntry`] into the store.
70    ///
71    /// If the new entry would be prefix-pruned immediately by a newer entry already in the store, this method returns `Ok(false)`, otherwise `Ok(true)`. If the new entry prefix-prunes older entries in the store, those older entries are automatically removed.
72    async fn insert_entry(
73        &mut self,
74        entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
75    ) -> Result<bool, Self::InternalError>;
76
77    /// Removes an entry and its payload from the store. Return `Ok(true)` if data was actually removed, and `Ok(false)` if there was no matching entry in the first place.
78    ///
79    /// If the `expected_digest` is `Some(pd)`, then the addressed entry is only forgotten if its payload digest is `pd`. If the payload digest does not match, the method returns `Ok(false)`.
80    ///
81    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent [joins](https://willowprotocol.org/specs/data-model/index.html#store_join) with other [`Store`]s may bring the forgotten entry back.
82    async fn forget_entry<K>(
83        &mut self,
84        namespace_id: &N,
85        key: &K,
86        expected_digest: Option<PD>,
87    ) -> Result<bool, Self::InternalError>
88    where
89        K: Keylike<MCL, MCC, MPL, S>,
90        PD: PartialEq;
91
92    /// Removes all entries and their payloads in some [`Area`] from the store.
93    ///
94    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent [joins](https://willowprotocol.org/specs/data-model/index.html#store_join) with other [`Store`]s may bring forgotten entries back.
95    async fn forget_area(
96        &mut self,
97        namespace_id: &N,
98        area: &Area<MCL, MCC, MPL, S>,
99    ) -> Result<(), Self::InternalError>;
100
101    /// Removes all entries and their payloads in some namespace from the store.
102    ///
103    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent [joins](https://willowprotocol.org/specs/data-model/index.html#store_join) with other [`Store`]s may bring forgotten entries back.
104    async fn forget_namespace(&mut self, namespace_id: &N) -> Result<(), Self::InternalError>;
105
106    /// Gets an entry (or `None` if there is none) together with a producer its payload (more specifically, the indicated `payload_slice`).
107    ///
108    /// If the `expected_digest` is `Some(pd)`, then the addressed entry is only returned if its payload digest is `pd`. If the payload digest does not match, the method returns `Ok(None)`.
109    ///
110    /// If the start of the indicated payload slice is strictly greater than its end, or if its start is strictly greater than the length of the stored payload, the behaviour of this method is unspecified.
111    async fn get_entry<K, Slice>(
112        &mut self,
113        namespace_id: &N,
114        key: &K,
115        expected_digest: Option<PD>,
116        payload_slice: &Slice,
117    ) -> Result<
118        Option<(
119            AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
120            impl BulkProducer<Item = u8, Final = (), Error = PayloadProducerError<Self::InternalError>>,
121        )>,
122        Self::InternalError,
123    >
124    where
125        K: Keylike<MCL, MCC, MPL, S>,
126        Slice: RangeBounds<u64>;
127
128    /// Returns a producer of all entries in some [`Area`] from the store.
129    async fn get_area(
130        &mut self,
131        namespace_id: N,
132        area: Area<MCL, MCC, MPL, S>,
133    ) -> impl Producer<
134        Item = AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
135        Final = (),
136        Error = Self::InternalError,
137    >;
138
139    /// Flushes all prior operations. If the backing storage is persistent, all mutations up to that point will have been successfully persisted after a successful flush.
140    async fn flush(&mut self) -> Result<(), Self::InternalError>;
141}
142
143/// The possible successful outcomes of non-destructively inserting an [`AuthorisedEntry`] into a [`Store`].
144#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
145pub enum NondestructiveInsert<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
146    /// Inserted the entry, and no other data was pruned.
147    Success(AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>),
148    /// Inserting an entry would have pruned old entries, so instead the store was not modified at all.
149    Prevented,
150    /// The inserted entry would be pruned by a newer entry in the store, so it was not inserted in the first place.
151    Outdated,
152}
153
154/// The errors that can be encountered when producing a payload.
155#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
156pub enum PayloadProducerError<StoreError> {
157    /// The store implementation encountered an internal error.
158    StoreError(StoreError),
159    /// The entry payload has been pruned or was forgotten.
160    PayloadWasRemoved,
161}
162
163/// The errors that can be encountered when creating an entry (from a producer emitting the payload).
164#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
165pub enum CreateEntryError<StoreError, ProducerError, AuthorisationTokenError> {
166    /// The store implementation encountered an internal error.
167    StoreError(StoreError),
168    /// The payload producer produced a different number of bytes than the claimed length.
169    IncorrectPayloadLength,
170    /// The payload producer emitted an error.
171    ProducerError(ProducerError),
172    /// Could not create an authorisation token for the created entry.
173    AuthorisationTokenError(AuthorisationTokenError),
174}
175
176// TODO Traits for a future hierarchy:
177//
178// -Bab-based payload interaction
179// - Area-based summaries
180// - 3dRange-based queries (also summaries, possibly in subtrait)
181// - subscriptions