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