Skip to main content

aranya_runtime/storage/
mod.rs

1//! Interfaces for graph storage.
2//!
3//! The [`StorageProvider`] and [`Storage`] interfaces enable high-level
4//! actions on the graph. Traversing the graph is made simpler by splitting
5//! its [`Command`]s into [`Segment`]s. Updating the graph is possible using
6//! [`Perspective`]s, which represent a slice of state.
7
8use alloc::{boxed::Box, string::String, vec::Vec};
9use core::{fmt, ops::Deref};
10
11use buggy::{Bug, BugExt as _};
12use serde::{Deserialize, Serialize};
13
14use crate::{Address, CmdId, Command, PolicyId, Prior};
15
16pub mod linear;
17pub mod memory;
18
19#[cfg(feature = "low-mem-usage")]
20pub const MAX_COMMAND_LENGTH: usize = 400;
21#[cfg(not(feature = "low-mem-usage"))]
22pub const MAX_COMMAND_LENGTH: usize = 2048;
23
24aranya_crypto::custom_id! {
25    /// The ID of the graph, taken from initialization.
26    pub struct GraphId;
27}
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
30pub struct Location {
31    pub segment: usize,
32    pub command: usize,
33}
34
35impl From<(usize, usize)> for Location {
36    fn from((segment, command): (usize, usize)) -> Self {
37        Self::new(segment, command)
38    }
39}
40
41impl AsRef<Self> for Location {
42    fn as_ref(&self) -> &Self {
43        self
44    }
45}
46
47impl Location {
48    pub fn new(segment: usize, command: usize) -> Self {
49        Self { segment, command }
50    }
51
52    /// If this is not the first command in a segment, return a location
53    /// pointing to the previous command.
54    #[must_use]
55    pub fn previous(mut self) -> Option<Self> {
56        if let Some(n) = usize::checked_sub(self.command, 1) {
57            self.command = n;
58            Some(self)
59        } else {
60            None
61        }
62    }
63
64    /// Returns true if other location is in the same segment.
65    pub fn same_segment(self, other: Self) -> bool {
66        self.segment == other.segment
67    }
68}
69
70impl fmt::Display for Location {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}:{}", self.segment, self.command)
73    }
74}
75
76/// An error returned by [`Storage`] or [`StorageProvider`].
77#[derive(Debug, PartialEq, Eq, thiserror::Error)]
78pub enum StorageError {
79    #[error("storage already exists")]
80    StorageExists,
81    #[error("no such storage")]
82    NoSuchStorage,
83    #[error("segment index {} is out of bounds", .0.segment)]
84    SegmentOutOfBounds(Location),
85    #[error("command index {} is out of bounds in segment {}", .0.command, .0.segment)]
86    CommandOutOfBounds(Location),
87    #[error("IO error")]
88    IoError,
89    #[error("not a merge command")]
90    NotMerge,
91    #[error("command with id {0} not found")]
92    NoSuchId(CmdId),
93    #[error("policy mismatch")]
94    PolicyMismatch,
95    #[error("cannot write an empty perspective")]
96    EmptyPerspective,
97    #[error("segment must be a descendant of the head for commit")]
98    HeadNotAncestor,
99    #[error("command's parents do not match the perspective head")]
100    PerspectiveHeadMismatch,
101    #[error(transparent)]
102    Bug(#[from] Bug),
103}
104
105/// Handle to storage implementations used by the runtime.
106pub trait StorageProvider {
107    type Perspective: Perspective + Revertable;
108    type Segment: Segment;
109    type Storage: Storage<
110            Segment = Self::Segment,
111            Perspective = Self::Perspective,
112            FactIndex = <Self::Segment as Segment>::FactIndex,
113        >;
114
115    /// Create an unrooted perspective, intended for creating a new graph.
116    ///
117    /// # Arguments
118    ///
119    /// * `policy_id` - The policy to associate with the graph.
120    fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective;
121
122    /// Create a new graph.
123    ///
124    /// # Arguments
125    ///
126    /// * `graph` - ID of the graph, taken from the initialization command.
127    /// * `init` - Contains the data necessary to initialize the new graph.
128    fn new_storage(
129        &mut self,
130        init: Self::Perspective,
131    ) -> Result<(GraphId, &mut Self::Storage), StorageError>;
132
133    /// Get an existing graph.
134    ///
135    /// # Arguments
136    ///
137    /// * `graph` - ID of the graph, taken from the initialization command.
138    fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError>;
139
140    /// Remove a graph.
141    ///
142    /// # Arguments
143    ///
144    /// * `graph` - ID of the graph, taken from the initialization command.
145    fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError>;
146
147    /// Gets a list of all stored graphs by their graph ID.
148    // TODO(nikki): rewrite this once we can use coroutines/generators?
149    fn list_graph_ids(
150        &mut self,
151    ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError>;
152}
153
154/// Represents the runtime's graph; [`Command`]s in storage have been validated
155/// by an associated policy and committed to state.
156pub trait Storage {
157    type Perspective: Perspective + Revertable;
158    type FactPerspective: FactPerspective;
159    type Segment: Segment<FactIndex = Self::FactIndex>;
160    type FactIndex: FactIndex;
161
162    /// Returns the location of Command with id if it has been stored by
163    /// searching from the head.
164    fn get_location(&self, address: Address) -> Result<Option<Location>, StorageError> {
165        self.get_location_from(self.get_head()?, address)
166    }
167
168    /// Returns the location of Command with id by searching from the given location.
169    fn get_location_from(
170        &self,
171        start: Location,
172        address: Address,
173    ) -> Result<Option<Location>, StorageError> {
174        let mut queue = Vec::new();
175        queue.push(start);
176        'outer: while let Some(loc) = queue.pop() {
177            let head = self.get_segment(loc)?;
178            if address.max_cut > head.longest_max_cut()? {
179                continue;
180            }
181            if let Some(loc) = head.get_by_address(address) {
182                return Ok(Some(loc));
183            }
184            // Assumes skip list is sorted in ascending order.
185            // We always want to skip as close to the root as possible.
186            for (skip, max_cut) in head.skip_list() {
187                if max_cut >= &address.max_cut {
188                    queue.push(*skip);
189                    continue 'outer;
190                }
191            }
192            queue.extend(head.prior());
193        }
194        Ok(None)
195    }
196
197    /// Returns the address of the command at the given location.
198    ///
199    /// By default, this fetches the segment, then the command, then the address.
200    fn get_command_address(&self, location: Location) -> Result<Address, StorageError> {
201        let segment = self.get_segment(location)?;
202        let command = segment
203            .get_command(location)
204            .ok_or(StorageError::CommandOutOfBounds(location))?;
205        let address = command.address()?;
206        Ok(address)
207    }
208
209    /// Returns a linear perspective at the given location.
210    fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError>;
211
212    /// Returns a fact perspective at the given location, intended for evaluating braids.
213    /// The fact perspective will include the facts of the command at the given location.
214    fn get_fact_perspective(&self, first: Location) -> Result<Self::FactPerspective, StorageError>;
215
216    /// Returns a merge perspective based on the given locations with the braid as prior facts.
217    fn new_merge_perspective(
218        &self,
219        left: Location,
220        right: Location,
221        last_common_ancestor: (Location, usize),
222        policy_id: PolicyId,
223        braid: Self::FactIndex,
224    ) -> Result<Self::Perspective, StorageError>;
225
226    /// Returns the segment at the given location.
227    fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError>;
228
229    /// Returns the location of head of the graph.
230    fn get_head(&self) -> Result<Location, StorageError>;
231
232    /// Returns the address of the head of the graph.
233    fn get_head_address(&self) -> Result<Address, StorageError> {
234        self.get_command_address(self.get_head()?)
235    }
236
237    /// Sets the given segment as the head of the graph.  Returns an error if
238    /// the current head is not an ancestor of the provided segment.
239    fn commit(&mut self, segment: Self::Segment) -> Result<(), StorageError>;
240
241    /// Writes the given perspective to a segment.
242    fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError>;
243
244    /// Writes the given fact perspective to a fact index.
245    fn write_facts(
246        &mut self,
247        fact_perspective: Self::FactPerspective,
248    ) -> Result<Self::FactIndex, StorageError>;
249
250    /// Determine whether the given location is an ancestor of the given segment.
251    fn is_ancestor(
252        &self,
253        search_location: Location,
254        segment: &Self::Segment,
255    ) -> Result<bool, StorageError> {
256        let mut queue = Vec::new();
257        queue.extend(segment.prior());
258        let segment = self.get_segment(search_location)?;
259        let address = segment
260            .get_command(search_location)
261            .assume("location must exist")?
262            .address()?;
263        'outer: while let Some(location) = queue.pop() {
264            if location.segment == search_location.segment
265                && location.command >= search_location.command
266            {
267                return Ok(true);
268            }
269            let segment = self.get_segment(location)?;
270            if address.max_cut > segment.longest_max_cut()? {
271                continue;
272            }
273            for (skip, max_cut) in segment.skip_list() {
274                if max_cut >= &address.max_cut {
275                    queue.push(*skip);
276                    continue 'outer;
277                }
278            }
279            queue.extend(segment.prior());
280        }
281        Ok(false)
282    }
283}
284
285type MaxCut = usize;
286
287/// A segment is a nonempty sequence of commands persisted to storage.
288///
289/// A segment can be one of three types. This might be encoded in a future version of the API.
290/// * init   - This segment is the first segment of the graph and begins with an init command.
291/// * linear - This segment has a single prior command and is simply a sequence of linear commands.
292/// * merge  - This segment merges two other segments and thus begins with a merge command. A merge
293///   segment has a braid as it's prior facts.
294///
295/// Each command past the first must have the parent of the previous command in the segment.
296pub trait Segment {
297    type FactIndex: FactIndex;
298    type Command<'a>: Command
299    where
300        Self: 'a;
301
302    /// Returns the head of the segment.
303    fn head(&self) -> Result<Self::Command<'_>, StorageError>;
304
305    /// Returns the first Command in the segment.
306    fn first(&self) -> Self::Command<'_>;
307
308    /// Returns the location of the head of the segment.
309    fn head_location(&self) -> Location;
310
311    /// Returns the location of the first command.
312    fn first_location(&self) -> Location;
313
314    /// Returns true if the segment contains the location.
315    fn contains(&self, location: Location) -> bool;
316
317    /// Returns the id for the policy used for this segment.
318    fn policy(&self) -> PolicyId;
319
320    /// Returns the prior segments for this segment.
321    fn prior(&self) -> Prior<Location>;
322
323    /// Returns the command at the given location.
324    fn get_command(&self, location: Location) -> Option<Self::Command<'_>>;
325
326    /// Returns the location of the command with the given address from within this segment.
327    fn get_by_address(&self, address: Address) -> Option<Location>;
328
329    /// Returns an iterator of commands starting at the given location.
330    fn get_from(&self, location: Location) -> Vec<Self::Command<'_>>;
331
332    /// Get the fact index associated with this segment.
333    fn facts(&self) -> Result<Self::FactIndex, StorageError>;
334
335    fn contains_any<I>(&self, locations: I) -> bool
336    where
337        I: IntoIterator,
338        I::Item: AsRef<Location>,
339    {
340        locations
341            .into_iter()
342            .any(|loc| self.contains(*loc.as_ref()))
343    }
344
345    /// The shortest max cut for this segment.
346    ///
347    /// This will always the max cut of the first command in the segment.
348    fn shortest_max_cut(&self) -> MaxCut;
349
350    /// The longest max cut for this segment.
351    ///
352    /// This will always be the max cut of the last command in the segment.
353    fn longest_max_cut(&self) -> Result<MaxCut, StorageError>;
354
355    /// The skip list is a series of locations that can be safely jumped to
356    /// when searching for a location. As long as the max cut of the location
357    /// you're jumping to is greater than or equal to the location you're
358    /// searching for you can jump to it and be guaranteed not to miss
359    /// the location you're searching for.
360    ///
361    /// For merge commands the last location in the skip list is the least
362    /// common ancestor.
363    fn skip_list(&self) -> &[(Location, MaxCut)];
364}
365
366/// An index of facts in storage.
367pub trait FactIndex: Query {}
368
369/// A perspective is essentially a mutable, in-memory version of a [`Segment`],
370/// with the same three types.
371pub trait Perspective: FactPerspective {
372    /// Returns the id for the policy used for this perspective.
373    fn policy(&self) -> PolicyId;
374
375    /// Adds the given command to the head of the perspective. The command's
376    /// parent must be the head of the perspective.
377    fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError>;
378
379    /// Returns true if the perspective contains a command with the given ID.
380    fn includes(&self, id: CmdId) -> bool;
381
382    /// Returns the head address in the perspective, if it exists
383    fn head_address(&self) -> Result<Prior<Address>, Bug>;
384}
385
386/// A fact perspective is essentially a mutable, in-memory version of a [`FactIndex`].
387pub trait FactPerspective: QueryMut {}
388
389/// A revertable perspective can make checkpoints and be reverted such that the
390/// state of the perspective matches that when the checkpoint was created.
391pub trait Revertable {
392    /// Create a checkpoint which can be used to revert the perspective.
393    fn checkpoint(&self) -> Checkpoint;
394
395    /// Revert the perspective to the state it was at when the checkpoint was created.
396    fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), Bug>;
397}
398
399/// A checkpoint used to revert perspectives.
400pub struct Checkpoint {
401    /// An index interpreted by a given `Revertable` implementation to revert to a prior point.
402    pub index: usize,
403}
404
405/// Can be queried to look up facts.
406///
407/// Facts are labeled by a name, which are generally a bounded set of human-readable strings determined in advance.
408///
409/// Within a name, facts are an association of compound keys to values. The facts are keyed by a compound key
410/// `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes. The fact value is also a sequence of bytes.
411pub trait Query {
412    /// Look up a named fact by an exact match of the compound key.
413    fn query(&self, name: &str, keys: &[Box<[u8]>]) -> Result<Option<Box<[u8]>>, StorageError>;
414
415    /// Iterator for [`Query::query_prefix`].
416    type QueryIterator: Iterator<Item = Result<Fact, StorageError>>;
417
418    /// Look up all named facts that begin with the prefix of keys, in sorted key order.
419    ///
420    /// The `prefix` is a partial compound key `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes.
421    /// This returns all facts under the name with keys such that `prefix` is equal to a prefix of the fact's keys.
422    fn query_prefix(
423        &self,
424        name: &str,
425        prefix: &[Box<[u8]>],
426    ) -> Result<Self::QueryIterator, StorageError>;
427}
428
429/// A fact with a key and value.
430#[derive(Debug, PartialEq, Eq)]
431pub struct Fact {
432    /// The sequence of keys.
433    pub key: Keys,
434    /// The bytes of the value.
435    pub value: Box<[u8]>,
436}
437
438/// Can mutate facts by inserting and deleting them.
439///
440/// See [`Query`] for details on the nature of facts.
441pub trait QueryMut: Query {
442    /// Insert a fact labeled by a name, with a given compound key and a value.
443    ///
444    /// This fact can later be looked up by [`Query`] methods, using the name and keys.
445    fn insert(&mut self, name: String, keys: Keys, value: Box<[u8]>);
446
447    /// Delete any fact associated to the compound key, under the given name.
448    fn delete(&mut self, name: String, keys: Keys);
449}
450
451/// A sequence of byte-based keys, used for facts.
452#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
453pub struct Keys(Box<[Box<[u8]>]>);
454
455impl Deref for Keys {
456    type Target = [Box<[u8]>];
457    fn deref(&self) -> &[Box<[u8]>] {
458        self.0.as_ref()
459    }
460}
461
462impl AsRef<[Box<[u8]>]> for Keys {
463    fn as_ref(&self) -> &[Box<[u8]>] {
464        self.0.as_ref()
465    }
466}
467
468impl core::borrow::Borrow<[Box<[u8]>]> for Keys {
469    fn borrow(&self) -> &[Box<[u8]>] {
470        self.0.as_ref()
471    }
472}
473
474impl From<&[&[u8]]> for Keys {
475    fn from(value: &[&[u8]]) -> Self {
476        value.iter().copied().collect()
477    }
478}
479
480impl Keys {
481    fn starts_with(&self, prefix: &[Box<[u8]>]) -> bool {
482        self.as_ref().starts_with(prefix)
483    }
484}
485
486impl<B: Into<Box<[u8]>>> FromIterator<B> for Keys {
487    fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
488        Self(iter.into_iter().map(Into::into).collect())
489    }
490}
491
492// TODO: Fix and enable
493// #[cfg(test)]
494// mod tests;