aranya_runtime/
command.rs

1use alloc::{
2    format,
3    string::{String, ToString},
4};
5
6use buggy::{Bug, BugExt};
7use serde::{Deserialize, Serialize};
8
9use crate::Prior;
10
11aranya_crypto::custom_id! {
12    /// An ID constructed as a cryptographic hash of a serialized [`Command`].
13    pub struct CommandId;
14}
15
16impl CommandId {
17    /// Derives a [`CommandId`] from some data.
18    ///
19    /// This is for testing only. It's not `#[cfg(test)]` because
20    /// (unfortunately) some code already depends on it.
21    pub fn hash_for_testing_only(data: &[u8]) -> Self {
22        use aranya_crypto::{hash::Hash, rust::Sha512};
23        Sha512::hash(data).into_array().into()
24    }
25
26    pub fn short_b58(&self) -> String {
27        #![allow(clippy::arithmetic_side_effects)]
28        let b58 = self.to_string();
29        let trimmed = b58.trim_start_matches('1');
30        let len = trimmed.len();
31        if len == 0 {
32            "1".into()
33        } else if len > 8 {
34            format!("..{}", &trimmed[len - 6..])
35        } else {
36            trimmed.into()
37        }
38    }
39}
40
41/// Identify how the client will sort the associated [`Command`].
42// Note: Order of variants affects derived Ord: Merge is least and Init is greatest.
43#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
44pub enum Priority {
45    /// Indicates two branches in the parent graph have been merged at this
46    /// command. A command with this priority must have two parents,
47    /// `Parent::Merge`.
48    Merge,
49    /// Indicates a user-specific action; the runtime uses the internal u32
50    /// for ordering.
51    Basic(u32),
52    /// Indicates all preceding commands are ancestors of this command.
53    Finalize,
54    /// Indicates state is initialized; the associated command is a common
55    /// ancestor to all other commands in the graph. A command with this
56    /// priority must have no parents, `Parent::None`.
57    Init,
58}
59
60/// An action message interpreted by its associated policy to affect state.
61///
62/// A [`Command`] is opaque to the runtime engine. When the engine receives a
63/// message, it is validated and serialized by its policy. The policy
64/// returns a command implementation to update the stored graph. A
65/// policy will also emit effects once a command is verified,
66/// which are sent to the client.
67pub trait Command {
68    /// Return this command's [`Priority`], determining how this event is
69    /// ordered amongst others it does not have a causal relationship with.
70    fn priority(&self) -> Priority;
71
72    /// Uniquely identifies the serialized command.
73    fn id(&self) -> CommandId;
74
75    /// Return this command's parents, or address(s) that immediately
76    /// precede(s) this.
77    fn parent(&self) -> Prior<Address>;
78
79    /// Return this command's associated policy.
80    fn policy(&self) -> Option<&[u8]>;
81
82    /// Return this command's serialized data.
83    fn bytes(&self) -> &[u8];
84
85    /// Return this command's max cut. Max cut is the maximum distance to the init command.
86    fn max_cut(&self) -> Result<usize, Bug> {
87        match self.parent() {
88            Prior::None => Ok(0),
89            Prior::Single(l) => Ok(l.max_cut.checked_add(1).assume("must not overflow")?),
90            Prior::Merge(l, r) => Ok(l
91                .max_cut
92                .max(r.max_cut)
93                .checked_add(1)
94                .assume("must not overflow")?),
95        }
96    }
97
98    /// Return this command's address.
99    fn address(&self) -> Result<Address, Bug> {
100        Ok(Address {
101            id: self.id(),
102            max_cut: self.max_cut()?,
103        })
104    }
105}
106
107impl<C: Command> Command for &C {
108    fn priority(&self) -> Priority {
109        (*self).priority()
110    }
111
112    fn id(&self) -> CommandId {
113        (*self).id()
114    }
115
116    fn parent(&self) -> Prior<Address> {
117        (*self).parent()
118    }
119
120    fn policy(&self) -> Option<&[u8]> {
121        (*self).policy()
122    }
123
124    fn bytes(&self) -> &[u8] {
125        (*self).bytes()
126    }
127
128    fn max_cut(&self) -> Result<usize, Bug> {
129        (*self).max_cut()
130    }
131
132    fn address(&self) -> Result<Address, Bug> {
133        (*self).address()
134    }
135}
136
137#[derive(Debug, Serialize, Deserialize, Clone, Copy, Ord, PartialEq, PartialOrd, Eq, Default)]
138/// An address contains all of the information needed to find a command in
139/// another graph.
140///
141/// The command id identifies the command you're searching for and the
142/// max_cut allows that command to be found efficiently.
143pub struct Address {
144    pub id: CommandId,
145    pub max_cut: usize,
146}
147
148impl Prior<Address> {
149    /// Returns the max cut for the command that is after this prior.
150    pub fn next_max_cut(&self) -> Result<usize, Bug> {
151        Ok(match self {
152            Prior::None => 1,
153            Prior::Single(l) => l.max_cut.checked_add(1).assume("must not overflow")?,
154            Prior::Merge(l, r) => l
155                .max_cut
156                .max(r.max_cut)
157                .checked_add(1)
158                .assume("must not overflow")?,
159        })
160    }
161}
162
163#[cfg(test)]
164mod test {
165    use super::*;
166
167    #[test]
168    fn priority_ordering() {
169        assert!(Priority::Merge < Priority::Basic(0));
170        assert!(Priority::Basic(0) < Priority::Basic(1));
171        assert!(Priority::Basic(1) < Priority::Basic(u32::MAX));
172        assert!(Priority::Basic(u32::MAX) < Priority::Finalize);
173        assert!(Priority::Finalize < Priority::Init);
174    }
175}