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
22pub 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
98impl<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
108impl<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}