commonware_storage/adb/operation/
variable.rs

1use crate::{adb::operation, mmr::Location};
2use bytes::{Buf, BufMut};
3use commonware_codec::{
4    varint::UInt, Codec, EncodeSize, Error as CodecError, Read, ReadExt as _, Write,
5};
6use commonware_utils::{hex, Array};
7use core::fmt::Display;
8
9/// An operation applied to an authenticated database with a variable size value.
10#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
11pub enum Operation<K: Array, V: Codec> {
12    // Operations for immutable stores.
13    Set(K, V),
14    Commit(Option<V>),
15    // Operations for mutable stores.
16    Delete(K),
17    Update(K, V),
18    CommitFloor(Option<V>, Location),
19}
20
21impl<K: Array, V: Codec> Operation<K, V> {
22    /// If this is an operation involving a key, returns the key. Otherwise, returns None.
23    pub fn key(&self) -> Option<&K> {
24        match self {
25            Self::Set(key, _) => Some(key),
26            Self::Commit(_) => None,
27            Self::Delete(key) => Some(key),
28            Self::Update(key, _) => Some(key),
29            Self::CommitFloor(_, _) => None,
30        }
31    }
32
33    /// If this is an operation involving a value, returns the value. Otherwise, returns None.
34    pub fn value(&self) -> Option<&V> {
35        match self {
36            Self::Set(_, value) => Some(value),
37            Self::Commit(value) => value.as_ref(),
38            Self::Delete(_) => None,
39            Self::Update(_, value) => Some(value),
40            Self::CommitFloor(value, _) => value.as_ref(),
41        }
42    }
43
44    /// If this is an operation involving a value, returns the value. Otherwise, returns None.
45    pub fn into_value(self) -> Option<V> {
46        match self {
47            Self::Set(_, value) => Some(value),
48            Self::Commit(value) => value,
49            Self::Delete(_) => None,
50            Self::Update(_, value) => Some(value),
51            Self::CommitFloor(value, _) => value,
52        }
53    }
54}
55
56impl<K: Array, V: Codec> EncodeSize for Operation<K, V> {
57    fn encode_size(&self) -> usize {
58        1 + match self {
59            Self::Delete(_) => K::SIZE,
60            Self::Update(_, v) => K::SIZE + v.encode_size(),
61            Self::CommitFloor(v, floor_loc) => v.encode_size() + UInt(**floor_loc).encode_size(),
62            Self::Set(_, v) => K::SIZE + v.encode_size(),
63            Self::Commit(v) => v.encode_size(),
64        }
65    }
66}
67
68impl<K: Array, V: Codec> Write for Operation<K, V> {
69    fn write(&self, buf: &mut impl BufMut) {
70        match &self {
71            Self::Set(k, v) => {
72                operation::SET_CONTEXT.write(buf);
73                k.write(buf);
74                v.write(buf);
75            }
76            Self::Commit(v) => {
77                operation::COMMIT_CONTEXT.write(buf);
78                v.write(buf);
79            }
80            Self::Delete(k) => {
81                operation::DELETE_CONTEXT.write(buf);
82                k.write(buf);
83            }
84            Self::Update(k, v) => {
85                operation::UPDATE_CONTEXT.write(buf);
86                k.write(buf);
87                v.write(buf);
88            }
89            Self::CommitFloor(v, floor_loc) => {
90                operation::COMMIT_FLOOR_CONTEXT.write(buf);
91                v.write(buf);
92                UInt(**floor_loc).write(buf);
93            }
94        }
95    }
96}
97
98impl<K: Array, V: Codec> Read for Operation<K, V> {
99    type Cfg = <V as Read>::Cfg;
100
101    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
102        match u8::read(buf)? {
103            operation::SET_CONTEXT => {
104                let key = K::read(buf)?;
105                let value = V::read_cfg(buf, cfg)?;
106                Ok(Self::Set(key, value))
107            }
108            operation::COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
109            operation::DELETE_CONTEXT => {
110                let key = K::read(buf)?;
111                Ok(Self::Delete(key))
112            }
113            operation::UPDATE_CONTEXT => {
114                let key = K::read(buf)?;
115                let value = V::read_cfg(buf, cfg)?;
116                Ok(Self::Update(key, value))
117            }
118            operation::COMMIT_FLOOR_CONTEXT => {
119                let metadata = Option::<V>::read_cfg(buf, cfg)?;
120                let floor_loc = UInt::read(buf)?;
121                let floor_loc = Location::new(floor_loc.into()).ok_or_else(|| {
122                    CodecError::Invalid(
123                        "storage::adb::operation::Operation",
124                        "commit floor location overflow",
125                    )
126                })?;
127                Ok(Self::CommitFloor(metadata, floor_loc))
128            }
129            e => Err(CodecError::InvalidEnum(e)),
130        }
131    }
132}
133
134impl<K: Array, V: Codec> Display for Operation<K, V> {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        match self {
137            Self::Set(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
138            Self::Commit(value) => {
139                if let Some(value) = value {
140                    write!(f, "[commit {}]", hex(&value.encode()))
141                } else {
142                    write!(f, "[commit]")
143                }
144            }
145            Self::Delete(key) => write!(f, "[key:{key} <deleted>]"),
146            Self::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
147            Self::CommitFloor(value, loc) => {
148                if let Some(value) = value {
149                    write!(
150                        f,
151                        "[commit {} with inactivity floor: {loc}]",
152                        hex(&value.encode())
153                    )
154                } else {
155                    write!(f, "[commit with inactivity floor: {loc}]")
156                }
157            }
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize as _};
166    use commonware_utils::sequence::U64;
167
168    #[test]
169    fn test_operation_key() {
170        let key = U64::new(1234);
171        let value = U64::new(56789);
172
173        let set_op = Operation::Set(key.clone(), value.clone());
174        assert_eq!(&key, set_op.key().unwrap());
175
176        let update_op = Operation::Update(key.clone(), value.clone());
177        assert_eq!(&key, update_op.key().unwrap());
178
179        let delete_op = Operation::<U64, U64>::Delete(key.clone());
180        assert_eq!(&key, delete_op.key().unwrap());
181
182        let commit_op = Operation::<U64, U64>::Commit(Some(value));
183        assert_eq!(None, commit_op.key());
184
185        let commit_floor_op = Operation::<U64, U64>::CommitFloor(None, Location::new_unchecked(42));
186        assert_eq!(None, commit_floor_op.key());
187    }
188
189    #[test]
190    fn test_operation_value() {
191        let key = U64::new(1234);
192        let value = U64::new(56789);
193
194        let set_op = Operation::Set(key.clone(), value.clone());
195        assert_eq!(&value, set_op.value().unwrap());
196
197        let update_op = Operation::Update(key.clone(), value.clone());
198        assert_eq!(&value, update_op.value().unwrap());
199
200        let delete_op = Operation::<U64, U64>::Delete(key.clone());
201        assert_eq!(None, delete_op.value());
202
203        let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
204        assert_eq!(&value, commit_op.value().unwrap());
205
206        let commit_op_none = Operation::<U64, U64>::Commit(None);
207        assert_eq!(None, commit_op_none.value());
208
209        let commit_floor_op =
210            Operation::<U64, U64>::CommitFloor(Some(value.clone()), Location::new_unchecked(42));
211        assert_eq!(&value, commit_floor_op.value().unwrap());
212
213        let commit_floor_op_none =
214            Operation::<U64, U64>::CommitFloor(None, Location::new_unchecked(42));
215        assert_eq!(None, commit_floor_op_none.value());
216    }
217
218    #[test]
219    fn test_operation_into_value() {
220        let key = U64::new(1234);
221        let value = U64::new(56789);
222
223        let set_op = Operation::Set(key.clone(), value.clone());
224        assert_eq!(value.clone(), set_op.into_value().unwrap());
225
226        let update_op = Operation::Update(key.clone(), value.clone());
227        assert_eq!(value.clone(), update_op.into_value().unwrap());
228
229        let delete_op = Operation::<U64, U64>::Delete(key.clone());
230        assert_eq!(None, delete_op.into_value());
231
232        let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
233        assert_eq!(value.clone(), commit_op.into_value().unwrap());
234
235        let commit_op_none = Operation::<U64, U64>::Commit(None);
236        assert_eq!(None, commit_op_none.into_value());
237
238        let commit_floor_op =
239            Operation::<U64, U64>::CommitFloor(Some(value.clone()), Location::new_unchecked(42));
240        assert_eq!(value, commit_floor_op.into_value().unwrap());
241
242        let commit_floor_op_none =
243            Operation::<U64, U64>::CommitFloor(None, Location::new_unchecked(42));
244        assert_eq!(None, commit_floor_op_none.into_value());
245    }
246
247    #[test]
248    fn test_operation_encode_decode() {
249        let key = U64::new(1234);
250        let value = U64::new(56789);
251
252        // Test Set operation
253        let set_op = Operation::Set(key.clone(), value.clone());
254        let encoded = set_op.encode();
255        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
256        assert_eq!(set_op, decoded);
257
258        // Test Update operation
259        let update_op = Operation::Update(key.clone(), value.clone());
260        let encoded = update_op.encode();
261        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
262        assert_eq!(update_op, decoded);
263
264        // Test Delete operation
265        let delete_op = Operation::<U64, U64>::Delete(key.clone());
266        let encoded = delete_op.encode();
267        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
268        assert_eq!(delete_op, decoded);
269
270        // Test Commit operation with value
271        let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
272        let encoded = commit_op.encode();
273        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
274        assert_eq!(commit_op, decoded);
275
276        // Test Commit operation without value
277        let commit_op = Operation::<U64, U64>::Commit(None);
278        let encoded = commit_op.encode();
279        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
280        assert_eq!(commit_op, decoded);
281
282        // Test CommitFloor operation with value
283        let commit_floor_op =
284            Operation::<U64, U64>::CommitFloor(Some(value.clone()), Location::new_unchecked(42));
285        let encoded = commit_floor_op.encode();
286        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
287        assert_eq!(commit_floor_op, decoded);
288
289        // Test CommitFloor operation without value
290        let commit_floor_op = Operation::<U64, U64>::CommitFloor(None, Location::new_unchecked(42));
291        let encoded = commit_floor_op.encode();
292        let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
293        assert_eq!(commit_floor_op, decoded);
294    }
295
296    #[test]
297    fn test_operation_encode_size() {
298        let key = U64::new(1234);
299        let value = U64::new(56789);
300
301        // Test Set operation
302        let set_op = Operation::Set(key.clone(), value.clone());
303        assert_eq!(set_op.encode_size(), 1 + U64::SIZE + value.encode_size());
304        assert_eq!(set_op.encode().len(), set_op.encode_size());
305
306        // Test Update operation
307        let update_op = Operation::Update(key.clone(), value.clone());
308        assert_eq!(update_op.encode_size(), 1 + U64::SIZE + value.encode_size());
309        assert_eq!(update_op.encode().len(), update_op.encode_size());
310
311        // Test Delete operation
312        let delete_op = Operation::<U64, U64>::Delete(key.clone());
313        assert_eq!(delete_op.encode_size(), 1 + U64::SIZE);
314        assert_eq!(delete_op.encode().len(), delete_op.encode_size());
315
316        // Test Commit operation with value
317        let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
318        assert_eq!(
319            commit_op.encode_size(),
320            1 + Some(value.clone()).encode_size()
321        );
322        assert_eq!(commit_op.encode().len(), commit_op.encode_size());
323
324        // Test Commit operation without value
325        let commit_op = Operation::<U64, U64>::Commit(None);
326        assert_eq!(
327            commit_op.encode_size(),
328            1 + Option::<U64>::None.encode_size()
329        );
330        assert_eq!(commit_op.encode().len(), commit_op.encode_size());
331
332        // Test CommitFloor operation
333        let commit_floor_op =
334            Operation::<U64, U64>::CommitFloor(Some(value.clone()), Location::new_unchecked(42));
335        assert_eq!(
336            commit_floor_op.encode_size(),
337            1 + Some(value).encode_size() + UInt(42u64).encode_size()
338        );
339        assert_eq!(
340            commit_floor_op.encode().len(),
341            commit_floor_op.encode_size()
342        );
343    }
344
345    #[test]
346    fn test_operation_display() {
347        let key = U64::new(1234);
348        let value = U64::new(56789);
349
350        // Test Set operation
351        let set_op = Operation::Set(key.clone(), value.clone());
352        assert_eq!(
353            format!("{set_op}"),
354            format!("[key:{key} value:{}]", hex(&value.encode()))
355        );
356
357        // Test Update operation
358        let update_op = Operation::Update(key.clone(), value.clone());
359        assert_eq!(
360            format!("{update_op}"),
361            format!("[key:{key} value:{}]", hex(&value.encode()))
362        );
363
364        // Test Delete operation
365        let delete_op = Operation::<U64, U64>::Delete(key.clone());
366        assert_eq!(format!("{delete_op}"), format!("[key:{key} <deleted>]"));
367
368        // Test Commit operation with value
369        let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
370        assert_eq!(
371            format!("{commit_op}"),
372            format!("[commit {}]", hex(&value.encode()))
373        );
374
375        // Test Commit operation without value
376        let commit_op = Operation::<U64, U64>::Commit(None);
377        assert_eq!(format!("{commit_op}"), "[commit]");
378
379        // Test CommitFloor operation with value
380        let commit_floor_op =
381            Operation::<U64, U64>::CommitFloor(Some(value.clone()), Location::new_unchecked(42));
382        assert_eq!(
383            format!("{commit_floor_op}"),
384            format!(
385                "[commit {} with inactivity floor: {}]",
386                hex(&value.encode()),
387                Location::new_unchecked(42)
388            )
389        );
390
391        // Test CommitFloor operation without value
392        let commit_floor_op = Operation::<U64, U64>::CommitFloor(None, Location::new_unchecked(42));
393        assert_eq!(
394            format!("{commit_floor_op}"),
395            format!(
396                "[commit with inactivity floor: {}]",
397                Location::new_unchecked(42)
398            )
399        );
400    }
401
402    #[test]
403    fn test_operation_invalid_context() {
404        let invalid = vec![0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0];
405        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
406        assert!(matches!(
407            decoded.unwrap_err(),
408            CodecError::InvalidEnum(0xFF)
409        ));
410    }
411
412    #[test]
413    fn test_operation_commit_floor_location_overflow() {
414        use crate::mmr::MAX_LOCATION;
415
416        // Create a commit floor operation with a valid location value
417        let valid_loc = MAX_LOCATION / 2;
418        let mut encoded = vec![operation::COMMIT_FLOOR_CONTEXT];
419        // Add empty metadata
420        encoded.push(0); // None for Option<V>
421                         // Add valid location as varint
422        encoded.extend_from_slice(&UInt(valid_loc).encode());
423
424        let decoded = Operation::<U64, U64>::decode(encoded.as_ref());
425        assert!(decoded.is_ok());
426        if let Ok(Operation::CommitFloor(None, loc)) = decoded {
427            assert_eq!(*loc, valid_loc);
428        } else {
429            panic!("Expected CommitFloor operation with valid location");
430        }
431
432        // Test with MAX_LOCATION - should be valid
433        let mut encoded = vec![operation::COMMIT_FLOOR_CONTEXT];
434        encoded.push(0); // None for Option<V>
435        encoded.extend_from_slice(&UInt(MAX_LOCATION).encode());
436
437        let decoded = Operation::<U64, U64>::decode(encoded.as_ref());
438        assert!(decoded.is_ok());
439
440        // Test with MAX_LOCATION + 1 - should fail
441        let mut encoded = vec![operation::COMMIT_FLOOR_CONTEXT];
442        encoded.push(0); // None for Option<V>
443        encoded.extend_from_slice(&UInt(MAX_LOCATION + 1).encode());
444
445        let decoded = Operation::<U64, U64>::decode(encoded.as_ref());
446        assert!(matches!(
447            decoded.unwrap_err(),
448            CodecError::Invalid(_, "commit floor location overflow")
449        ));
450    }
451
452    #[test]
453    fn test_operation_insufficient_buffer() {
454        // Test insufficient buffer for Set operation
455        let invalid = vec![operation::SET_CONTEXT];
456        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
457        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
458
459        // Test insufficient buffer for Delete operation
460        let invalid = vec![operation::DELETE_CONTEXT];
461        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
462        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
463
464        // Test insufficient buffer for Update operation
465        let invalid = vec![operation::UPDATE_CONTEXT];
466        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
467        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
468
469        // Test insufficient buffer for Commit operation
470        let invalid = vec![operation::COMMIT_CONTEXT];
471        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
472        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
473
474        // Test insufficient buffer for CommitFloor operation
475        let invalid = vec![operation::COMMIT_FLOOR_CONTEXT];
476        let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
477        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
478    }
479
480    #[test]
481    fn test_operation_roundtrip_all_variants() {
482        let key1 = U64::new(100);
483        let key2 = U64::new(200);
484        let value1 = U64::new(1000);
485        let value2 = U64::new(2000);
486        let location = Location::new_unchecked(999);
487
488        // Test all operation variants
489        let operations: Vec<Operation<U64, U64>> = vec![
490            Operation::Set(key1.clone(), value1.clone()),
491            Operation::Update(key2.clone(), value2.clone()),
492            Operation::Delete(key1.clone()),
493            Operation::Commit(Some(value1.clone())),
494            Operation::Commit(None),
495            Operation::CommitFloor(Some(value2.clone()), location),
496            Operation::CommitFloor(None, location),
497        ];
498
499        for op in operations {
500            let encoded = op.encode();
501            let decoded = Operation::<U64, U64>::decode(encoded.clone()).unwrap();
502            assert_eq!(op, decoded, "Failed to roundtrip: {op:?}");
503            assert_eq!(encoded.len(), op.encode_size(), "Size mismatch for: {op:?}");
504        }
505    }
506
507    #[derive(Clone, Debug, PartialEq, Eq)]
508    struct VariableSizeValue(Vec<u8>);
509
510    impl Write for VariableSizeValue {
511        fn write(&self, buf: &mut impl BufMut) {
512            UInt(self.0.len() as u64).write(buf);
513            buf.put_slice(&self.0);
514        }
515    }
516
517    impl EncodeSize for VariableSizeValue {
518        fn encode_size(&self) -> usize {
519            UInt(self.0.len() as u64).encode_size() + self.0.len()
520        }
521    }
522
523    impl Read for VariableSizeValue {
524        type Cfg = ();
525
526        fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, CodecError> {
527            let len = UInt::read(buf)?;
528            let len: u64 = len.into();
529            let len = usize::try_from(len)
530                .map_err(|_| CodecError::Invalid("VariableSizeValue", "length overflow"))?;
531            if buf.remaining() < len {
532                return Err(CodecError::EndOfBuffer);
533            }
534            let mut data = vec![0u8; len];
535            buf.copy_to_slice(&mut data);
536            Ok(VariableSizeValue(data))
537        }
538    }
539
540    #[test]
541    fn test_operation_variable_size_values() {
542        let key = U64::new(42);
543
544        // Test with different sized values
545        let small_value = VariableSizeValue(vec![1, 2, 3]);
546        let large_value = VariableSizeValue(vec![0xFF; 1000]);
547        let empty_value = VariableSizeValue(vec![]);
548
549        // Test Set with variable sizes
550        let set_small = Operation::Set(key.clone(), small_value.clone());
551        let encoded = set_small.encode();
552        let decoded = Operation::<U64, VariableSizeValue>::decode(encoded).unwrap();
553        assert_eq!(set_small, decoded);
554
555        let set_large = Operation::Set(key.clone(), large_value.clone());
556        let encoded = set_large.encode();
557        let decoded = Operation::<U64, VariableSizeValue>::decode(encoded).unwrap();
558        assert_eq!(set_large, decoded);
559
560        let set_empty = Operation::Set(key.clone(), empty_value.clone());
561        let encoded = set_empty.encode();
562        let decoded = Operation::<U64, VariableSizeValue>::decode(encoded).unwrap();
563        assert_eq!(set_empty, decoded);
564
565        // Test Commit with variable sizes
566        let commit = Operation::<U64, VariableSizeValue>::Commit(Some(large_value.clone()));
567        let encoded = commit.encode();
568        let decoded = Operation::<U64, VariableSizeValue>::decode(encoded).unwrap();
569        assert_eq!(commit, decoded);
570
571        // Test encode_size is accurate for variable sizes
572        assert_eq!(set_small.encode().len(), set_small.encode_size());
573        assert_eq!(set_large.encode().len(), set_large.encode_size());
574        assert_eq!(set_empty.encode().len(), set_empty.encode_size());
575    }
576}