Skip to main content

commonware_storage/qmdb/keyless/operation/
mod.rs

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