Skip to main content

commonware_storage/qmdb/keyless/operation/
mod.rs

1use crate::{
2    merkle::{Family, Location},
3    qmdb::{
4        any::value::ValueEncoding,
5        operation::{Committable, Floored},
6    },
7};
8use commonware_codec::{Encode as _, Error as CodecError, Read, Write};
9use commonware_formatting::hex;
10use commonware_runtime::{Buf, BufMut};
11use core::fmt::Display;
12
13pub(crate) mod fixed;
14pub(crate) mod variable;
15
16// Context byte prefixes for identifying the operation type.
17const COMMIT_CONTEXT: u8 = 0;
18const APPEND_CONTEXT: u8 = 1;
19
20/// Delegates Operation-level codec (Write, Read) to the value encoding.
21///
22/// Fixed and variable encodings have different wire formats. Fixed pads to a uniform size,
23/// variable does not. A single blanket `impl Write for Operation<F, V>` dispatches here, while
24/// the two impls of this trait (on FixedEncoding and VariableEncoding) live on different Self
25/// types and therefore do not overlap.
26pub trait Codec: ValueEncoding + Sized {
27    type ReadCfg: Clone + Send + Sync + 'static;
28
29    fn write_operation<F: Family>(op: &Operation<F, Self>, buf: &mut impl BufMut);
30    fn read_operation<F: Family>(
31        buf: &mut impl Buf,
32        cfg: &Self::ReadCfg,
33    ) -> Result<Operation<F, Self>, CodecError>;
34}
35
36/// Operations for keyless stores.
37#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
38pub enum Operation<F: Family, V: ValueEncoding> {
39    /// Wraps the value appended to the database by this operation.
40    Append(V::Value),
41
42    /// Indicates the database has been committed, carrying optional metadata and the inactivity
43    /// floor location declared by the application at commit time.
44    Commit(Option<V::Value>, Location<F>),
45}
46
47impl<F: Family, V: ValueEncoding> Operation<F, V> {
48    /// Returns the value (if any) wrapped by this operation.
49    pub fn into_value(self) -> Option<V::Value> {
50        match self {
51            Self::Append(value) => Some(value),
52            Self::Commit(value, _) => value,
53        }
54    }
55
56    /// Returns the inactivity floor location if this is a commit operation.
57    pub const fn has_floor(&self) -> Option<Location<F>> {
58        match self {
59            Self::Commit(_, loc) => Some(*loc),
60            Self::Append(_) => None,
61        }
62    }
63}
64
65impl<F: Family, V: Codec> Write for Operation<F, V> {
66    fn write(&self, buf: &mut impl BufMut) {
67        V::write_operation(self, buf)
68    }
69}
70
71impl<F: Family, V: Codec> Committable for Operation<F, V> {
72    fn is_commit(&self) -> bool {
73        matches!(self, Self::Commit(_, _))
74    }
75}
76
77impl<F: Family, V: ValueEncoding> Floored<F> for Operation<F, V> {
78    fn has_floor(&self) -> Option<Location<F>> {
79        self.has_floor()
80    }
81}
82
83impl<F: Family, V: Codec> Read for Operation<F, V> {
84    type Cfg = <V as Codec>::ReadCfg;
85
86    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
87        V::read_operation(buf, cfg)
88    }
89}
90
91impl<F: Family, V: ValueEncoding> Display for Operation<F, V> {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            Self::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
95            Self::Commit(value, floor) => {
96                if let Some(value) = value {
97                    write!(f, "[commit {} floor:{}]", hex(&value.encode()), **floor)
98                } else {
99                    write!(f, "[commit floor:{}]", **floor)
100                }
101            }
102        }
103    }
104}
105
106#[cfg(feature = "arbitrary")]
107impl<F: Family, V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<F, V>
108where
109    V::Value: for<'a> arbitrary::Arbitrary<'a>,
110{
111    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
112        let choice = u.int_in_range(0..=1)?;
113        match choice {
114            0 => Ok(Self::Append(V::Value::arbitrary(u)?)),
115            1 => {
116                let metadata = Option::<V::Value>::arbitrary(u)?;
117                let floor = Location::<F>::arbitrary(u)?;
118                Ok(Self::Commit(metadata, floor))
119            }
120            _ => unreachable!(),
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::{merkle::mmr, qmdb::any::value::VariableEncoding};
129    use commonware_codec::Encode;
130    use commonware_formatting::hex;
131    use commonware_utils::sequence::U64;
132
133    #[test]
134    fn display_append() {
135        let op = Operation::<mmr::Family, VariableEncoding<U64>>::Append(U64::new(12345));
136        assert_eq!(
137            format!("{op}"),
138            format!("[append value:{}]", hex(&U64::new(12345).encode()))
139        );
140    }
141
142    #[test]
143    fn display_commit_some() {
144        let op = Operation::<mmr::Family, VariableEncoding<U64>>::Commit(
145            Some(U64::new(42)),
146            Location::new(7),
147        );
148        assert_eq!(
149            format!("{op}"),
150            format!("[commit {} floor:7]", hex(&U64::new(42).encode()))
151        );
152    }
153
154    #[test]
155    fn display_commit_none() {
156        let op = Operation::<mmr::Family, VariableEncoding<U64>>::Commit(None, Location::new(3));
157        assert_eq!(format!("{op}"), "[commit floor:3]");
158    }
159
160    #[cfg(feature = "arbitrary")]
161    mod conformance {
162        use super::Operation;
163        use crate::{
164            merkle::mmr,
165            qmdb::any::value::{FixedEncoding, VariableEncoding},
166        };
167        use commonware_codec::conformance::CodecConformance;
168        use commonware_utils::sequence::U64;
169
170        commonware_conformance::conformance_tests! {
171            CodecConformance<Operation<mmr::Family, VariableEncoding<U64>>>,
172            CodecConformance<Operation<mmr::Family, FixedEncoding<U64>>>
173        }
174    }
175}