Skip to main content

commonware_storage/qmdb/any/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_runtime::{Buf, BufMut};
7use commonware_utils::hex;
8use std::fmt;
9
10pub(crate) mod fixed;
11pub(crate) mod update;
12pub(crate) mod variable;
13pub use update::Update;
14
15pub(crate) const DELETE_CONTEXT: u8 = 0xD1;
16pub(crate) const UPDATE_CONTEXT: u8 = 0xD2;
17pub(crate) const COMMIT_CONTEXT: u8 = 0xD3;
18
19pub type Ordered<F, K, V> = Operation<F, update::Ordered<K, V>>;
20pub type Unordered<F, K, V> = Operation<F, update::Unordered<K, V>>;
21
22/// Delegates Operation-level codec (Write, Read) to the value encoding.
23///
24/// Fixed and variable encodings have different wire formats. Fixed pads to a uniform size,
25/// variable does not. A single blanket `impl Write for Operation<F, S>` dispatches here, while the
26/// two impls of this trait (on FixedEncoding and VariableEncoding) live on different Self types
27/// and therefore do not overlap.
28pub trait OperationCodec<F: Family, S: Update<ValueEncoding = Self>>:
29    ValueEncoding + Sized
30{
31    type ReadCfg: Clone + Send + Sync + 'static;
32
33    fn write_operation(op: &Operation<F, S>, buf: &mut impl BufMut);
34    fn read_operation(
35        buf: &mut impl Buf,
36        cfg: &Self::ReadCfg,
37    ) -> Result<Operation<F, S>, CodecError>;
38}
39
40#[derive(Clone, PartialEq, Debug)]
41pub enum Operation<F: Family, S: Update> {
42    Delete(S::Key),
43    Update(S),
44    CommitFloor(Option<S::Value>, Location<F>),
45}
46
47#[cfg(feature = "arbitrary")]
48impl<F: Family, S: Update> arbitrary::Arbitrary<'_> for Operation<F, S>
49where
50    S::Key: for<'a> arbitrary::Arbitrary<'a>,
51    S::Value: for<'a> arbitrary::Arbitrary<'a>,
52    S: for<'a> arbitrary::Arbitrary<'a>,
53{
54    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
55        let choice = u.int_in_range(0..=2)?;
56        match choice {
57            0 => Ok(Self::Delete(u.arbitrary()?)),
58            1 => Ok(Self::Update(u.arbitrary()?)),
59            2 => Ok(Self::CommitFloor(u.arbitrary()?, u.arbitrary()?)),
60            _ => unreachable!(),
61        }
62    }
63}
64
65impl<F: Family, S: Update> crate::qmdb::operation::Operation<F> for Operation<F, S> {
66    type Key = S::Key;
67
68    fn key(&self) -> Option<&Self::Key> {
69        match self {
70            Self::Delete(k) => Some(k),
71            Self::Update(p) => Some(p.key()),
72            Self::CommitFloor(_, _) => None,
73        }
74    }
75
76    fn is_update(&self) -> bool {
77        matches!(self, Self::Update(_))
78    }
79
80    fn is_delete(&self) -> bool {
81        matches!(self, Self::Delete(_))
82    }
83
84    fn has_floor(&self) -> Option<Location<F>> {
85        match self {
86            Self::CommitFloor(_, loc) => Some(*loc),
87            _ => None,
88        }
89    }
90}
91
92impl<F: Family, S: Update> Committable for Operation<F, S> {
93    fn is_commit(&self) -> bool {
94        matches!(self, Self::CommitFloor(_, _))
95    }
96}
97
98// Blanket Write via delegation.
99impl<F: Family, S: Update> Write for Operation<F, S>
100where
101    S::ValueEncoding: OperationCodec<F, S>,
102{
103    fn write(&self, buf: &mut impl BufMut) {
104        S::ValueEncoding::write_operation(self, buf)
105    }
106}
107
108// Blanket Read via delegation.
109impl<F: Family, S: Update> Read for Operation<F, S>
110where
111    S::ValueEncoding: OperationCodec<F, S>,
112{
113    type Cfg = <S::ValueEncoding as OperationCodec<F, S>>::ReadCfg;
114
115    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
116        S::ValueEncoding::read_operation(buf, cfg)
117    }
118}
119
120impl<F: Family, S: Update> fmt::Display for Operation<F, S> {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Delete(key) => write!(f, "[key:{} <deleted>]", hex(key)),
124            Self::Update(payload) => payload.fmt(f),
125            Self::CommitFloor(value, loc) => {
126                if let Some(value) = value {
127                    write!(
128                        f,
129                        "[commit {} with inactivity floor: {loc}]",
130                        hex(&value.encode())
131                    )
132                } else {
133                    write!(f, "[commit with inactivity floor: {loc}]")
134                }
135            }
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::qmdb::any::value::{FixedEncoding, VariableEncoding};
144    use commonware_codec::{Codec, RangeCfg, Read};
145    use commonware_utils::sequence::FixedBytes;
146
147    type F = crate::merkle::mmr::Family;
148
149    fn roundtrip<T>(value: &T, cfg: &<T as Read>::Cfg)
150    where
151        T: Codec + PartialEq + std::fmt::Debug,
152    {
153        let encoded = value.encode();
154        let decoded = T::decode_cfg(encoded.clone(), cfg).expect("decode");
155        assert_eq!(decoded, *value);
156        let encoded2 = decoded.encode();
157        assert_eq!(encoded, encoded2);
158    }
159
160    #[test]
161    fn ordered_fixed_roundtrip() {
162        type K = FixedBytes<4>;
163        type V = u64;
164        type Op = Ordered<F, K, FixedEncoding<V>>;
165
166        let delete = Op::Delete(FixedBytes::from([1, 2, 3, 4]));
167        let update = Op::Update(update::Ordered {
168            key: FixedBytes::from([4, 3, 2, 1]),
169            value: 0xdead_beef_u64,
170            next_key: FixedBytes::from([9, 9, 9, 9]),
171        });
172        let commit_some = Op::CommitFloor(Some(123u64), crate::mmr::Location::new(5));
173        let commit_none = Op::CommitFloor(None, crate::mmr::Location::new(7));
174
175        roundtrip(&delete, &());
176        roundtrip(&update, &());
177        roundtrip(&commit_some, &());
178        roundtrip(&commit_none, &());
179    }
180
181    #[test]
182    fn unordered_fixed_roundtrip() {
183        type K = FixedBytes<4>;
184        type V = u64;
185        type Op = Unordered<F, K, FixedEncoding<V>>;
186
187        let delete = Op::Delete(FixedBytes::from([0, 0, 0, 1]));
188        let update = Op::Update(update::Unordered(FixedBytes::from([9, 8, 7, 6]), 77u64));
189        let commit = Op::CommitFloor(Some(555u64), crate::mmr::Location::new(3));
190
191        roundtrip(&delete, &());
192        roundtrip(&update, &());
193        roundtrip(&commit, &());
194    }
195
196    #[test]
197    fn ordered_variable_roundtrip() {
198        type K = FixedBytes<4>;
199        type V = Vec<u8>;
200        type Op = Ordered<F, K, VariableEncoding<V>>;
201        let cfg = ((), (RangeCfg::from(..), ()));
202
203        let delete = Op::Delete(FixedBytes::from([1, 1, 1, 1]));
204        let update = Op::Update(update::Ordered {
205            key: FixedBytes::from([2, 2, 2, 2]),
206            value: vec![1, 2, 3, 4, 5],
207            next_key: FixedBytes::from([3, 3, 3, 3]),
208        });
209        let commit_some = Op::CommitFloor(Some(vec![9, 9, 9]), crate::mmr::Location::new(9));
210        let commit_none = Op::CommitFloor(None, crate::mmr::Location::new(10));
211
212        roundtrip(&delete, &cfg);
213        roundtrip(&update, &cfg);
214        roundtrip(&commit_some, &cfg);
215        roundtrip(&commit_none, &cfg);
216    }
217
218    #[test]
219    fn unordered_variable_roundtrip() {
220        type K = FixedBytes<4>;
221        type V = Vec<u8>;
222        type Op = Unordered<F, K, VariableEncoding<V>>;
223        let cfg = ((), (RangeCfg::from(..), ()));
224
225        let delete = Op::Delete(FixedBytes::from([4, 4, 4, 4]));
226        let update = Op::Update(update::Unordered(
227            FixedBytes::from([5, 5, 5, 5]),
228            vec![7, 7, 7, 7],
229        ));
230        let commit = Op::CommitFloor(Some(vec![8, 8]), crate::mmr::Location::new(12));
231
232        roundtrip(&delete, &cfg);
233        roundtrip(&update, &cfg);
234        roundtrip(&commit, &cfg);
235    }
236
237    #[cfg(feature = "arbitrary")]
238    mod conformance {
239        use super::*;
240        use crate::{
241            merkle::{mmb, mmr},
242            qmdb::any::{
243                ordered::Operation as OrderedOperation, unordered::Operation as UnorderedOperation,
244            },
245        };
246        use commonware_codec::conformance::CodecConformance;
247        use commonware_utils::sequence::U64;
248
249        commonware_conformance::conformance_tests! {
250            CodecConformance<OrderedOperation<mmr::Family, U64, FixedEncoding<U64>>>,
251            CodecConformance<OrderedOperation<mmr::Family, U64, VariableEncoding<Vec<u8>>>>,
252            CodecConformance<UnorderedOperation<mmr::Family, U64, FixedEncoding<U64>>>,
253            CodecConformance<UnorderedOperation<mmr::Family, U64, VariableEncoding<Vec<u8>>>>,
254            CodecConformance<OrderedOperation<mmb::Family, U64, FixedEncoding<U64>>>,
255            CodecConformance<OrderedOperation<mmb::Family, U64, VariableEncoding<Vec<u8>>>>,
256            CodecConformance<UnorderedOperation<mmb::Family, U64, FixedEncoding<U64>>>,
257            CodecConformance<UnorderedOperation<mmb::Family, U64, VariableEncoding<Vec<u8>>>>,
258        }
259    }
260}