commonware_storage/store/
operation.rs

1//! Operations that can be applied to an authenticated database.
2//!
3//! The `Operation` enum implements the `Array` trait, allowing for a persistent log of operations
4//! based on a `crate::Journal`.
5
6use bytes::{Buf, BufMut};
7use commonware_codec::{
8    util::at_least, varint::UInt, Codec, CodecFixed, EncodeSize, Error as CodecError, FixedSize,
9    Read, ReadExt, Write,
10};
11use commonware_utils::{hex, Array};
12use std::{
13    cmp::{Ord, PartialOrd},
14    fmt::{Debug, Display},
15    hash::Hash,
16};
17use thiserror::Error;
18
19// Context byte prefixes for identifying the operation type.
20const DELETE_CONTEXT: u8 = 0;
21const UPDATE_CONTEXT: u8 = 1;
22const COMMIT_FLOOR_CONTEXT: u8 = 2;
23const SET_CONTEXT: u8 = 3;
24const COMMIT_CONTEXT: u8 = 4;
25const APPEND_CONTEXT: u8 = 5;
26
27/// Errors returned by operation functions.
28#[derive(Error, Debug)]
29pub enum Error {
30    #[error("invalid length")]
31    InvalidLength,
32    #[error("invalid key: {0}")]
33    InvalidKey(CodecError),
34    #[error("invalid value: {0}")]
35    InvalidValue(CodecError),
36    #[error("invalid context byte")]
37    InvalidContextByte,
38    #[error("delete operation has non-zero value")]
39    InvalidDeleteOp,
40    #[error("commit floor operation has non-zero bytes after location")]
41    InvalidCommitFloorOp,
42}
43
44/// An operation applied to an authenticated database with a fixed size value.
45#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
46pub enum Fixed<K: Array, V: CodecFixed> {
47    /// Indicates the key no longer has a value.
48    Delete(K),
49
50    /// Indicates the key now has the wrapped value.
51    Update(K, V),
52
53    /// Indicates all prior operations are no longer subject to rollback, and the floor on inactive
54    /// operations has been raised to the wrapped value.
55    CommitFloor(u64),
56}
57
58/// Operations for keyless stores.
59#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
60pub enum Keyless<V: Codec> {
61    /// Wraps the value appended to the database by this operation.
62    Append(V),
63
64    /// Indicates the database has been committed.
65    Commit(Option<V>),
66}
67
68/// An operation applied to an authenticated database with a variable size value.
69#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
70pub enum Variable<K: Array, V: Codec> {
71    // Operations for immutable stores.
72    Set(K, V),
73    Commit(Option<V>),
74    // Operations for mutable stores.
75    Delete(K),
76    Update(K, V),
77    CommitFloor(Option<V>, u64),
78}
79
80impl<K: Array, V: CodecFixed> FixedSize for Fixed<K, V> {
81    const SIZE: usize = u8::SIZE + K::SIZE + V::SIZE;
82}
83
84impl<K: Array, V: Codec> EncodeSize for Variable<K, V> {
85    fn encode_size(&self) -> usize {
86        1 + match self {
87            Variable::Delete(_) => K::SIZE,
88            Variable::Update(_, v) => K::SIZE + v.encode_size(),
89            Variable::CommitFloor(v, floor_loc) => v.encode_size() + UInt(*floor_loc).encode_size(),
90            Variable::Set(_, v) => K::SIZE + v.encode_size(),
91            Variable::Commit(v) => v.encode_size(),
92        }
93    }
94}
95
96impl<V: Codec> EncodeSize for Keyless<V> {
97    fn encode_size(&self) -> usize {
98        1 + match self {
99            Keyless::Append(v) => v.encode_size(),
100            Keyless::Commit(v) => v.encode_size(),
101        }
102    }
103}
104
105impl<K: Array, V: CodecFixed> Fixed<K, V> {
106    // A compile-time assertion that operation's array size is large enough to handle the commit
107    // operation, which requires 9 bytes.
108    const _MIN_OPERATION_LEN: usize = 9;
109    const _COMMIT_OP_ASSERT: () = assert!(
110        Self::SIZE >= Self::_MIN_OPERATION_LEN,
111        "array size too small for commit op"
112    );
113
114    /// If this is a [Fixed::Update] or [Fixed::Delete] operation, returns the key.
115    /// Otherwise, returns None.
116    pub fn key(&self) -> Option<&K> {
117        match self {
118            Fixed::Delete(key) => Some(key),
119            Fixed::Update(key, _) => Some(key),
120            Fixed::CommitFloor(_) => None,
121        }
122    }
123
124    /// If this is a [Fixed::Update] operation, returns the value.
125    /// Otherwise, returns None.
126    pub fn value(&self) -> Option<&V> {
127        match self {
128            Fixed::Delete(_) => None,
129            Fixed::Update(_, value) => Some(value),
130            Fixed::CommitFloor(_) => None,
131        }
132    }
133
134    /// If this is a [Fixed::Update] operation, returns the value.
135    /// Otherwise, returns None.
136    pub fn into_value(self) -> Option<V> {
137        match self {
138            Fixed::Delete(_) => None,
139            Fixed::Update(_, value) => Some(value),
140            Fixed::CommitFloor(_) => None,
141        }
142    }
143}
144
145impl<K: Array, V: Codec> Variable<K, V> {
146    /// If this is an operation involving a key, returns the key. Otherwise, returns None.
147    pub fn key(&self) -> Option<&K> {
148        match self {
149            Variable::Set(key, _) => Some(key),
150            Variable::Commit(_) => None,
151            Variable::Delete(key) => Some(key),
152            Variable::Update(key, _) => Some(key),
153            Variable::CommitFloor(_, _) => None,
154        }
155    }
156
157    /// If this is an operation involving a value, returns the value. Otherwise, returns None.
158    pub fn value(&self) -> Option<&V> {
159        match self {
160            Variable::Set(_, value) => Some(value),
161            Variable::Commit(value) => value.as_ref(),
162            Variable::Delete(_) => None,
163            Variable::Update(_, value) => Some(value),
164            Variable::CommitFloor(value, _) => value.as_ref(),
165        }
166    }
167
168    /// If this is an operation involving a value, returns the value. Otherwise, returns None.
169    pub fn into_value(self) -> Option<V> {
170        match self {
171            Variable::Set(_, value) => Some(value),
172            Variable::Commit(value) => value,
173            Variable::Delete(_) => None,
174            Variable::Update(_, value) => Some(value),
175            Variable::CommitFloor(value, _) => value,
176        }
177    }
178}
179
180impl<V: Codec> Write for Keyless<V> {
181    fn write(&self, buf: &mut impl BufMut) {
182        match &self {
183            Keyless::Append(value) => {
184                APPEND_CONTEXT.write(buf);
185                value.write(buf);
186            }
187            Keyless::Commit(metadata) => {
188                COMMIT_CONTEXT.write(buf);
189                metadata.write(buf);
190            }
191        }
192    }
193}
194
195impl<K: Array, V: CodecFixed> Write for Fixed<K, V> {
196    fn write(&self, buf: &mut impl BufMut) {
197        match &self {
198            Fixed::Delete(k) => {
199                DELETE_CONTEXT.write(buf);
200                k.write(buf);
201                // Pad with 0 up to [Self::SIZE]
202                buf.put_bytes(0, V::SIZE);
203            }
204            Fixed::Update(k, v) => {
205                UPDATE_CONTEXT.write(buf);
206                k.write(buf);
207                v.write(buf);
208            }
209            Fixed::CommitFloor(floor_loc) => {
210                COMMIT_FLOOR_CONTEXT.write(buf);
211                buf.put_slice(&floor_loc.to_be_bytes());
212                // Pad with 0 up to [Self::SIZE]
213                buf.put_bytes(0, Self::SIZE - 1 - u64::SIZE);
214            }
215        }
216    }
217}
218
219impl<K: Array, V: Codec> Write for Variable<K, V> {
220    fn write(&self, buf: &mut impl BufMut) {
221        match &self {
222            Variable::Set(k, v) => {
223                SET_CONTEXT.write(buf);
224                k.write(buf);
225                v.write(buf);
226            }
227            Variable::Commit(v) => {
228                COMMIT_CONTEXT.write(buf);
229                v.write(buf);
230            }
231            Variable::Delete(k) => {
232                DELETE_CONTEXT.write(buf);
233                k.write(buf);
234            }
235            Variable::Update(k, v) => {
236                UPDATE_CONTEXT.write(buf);
237                k.write(buf);
238                v.write(buf);
239            }
240            Variable::CommitFloor(v, floor_loc) => {
241                COMMIT_FLOOR_CONTEXT.write(buf);
242                v.write(buf);
243                UInt(*floor_loc).write(buf);
244            }
245        }
246    }
247}
248
249impl<V: Codec> Read for Keyless<V> {
250    type Cfg = <V as Read>::Cfg;
251
252    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
253        match u8::read(buf)? {
254            APPEND_CONTEXT => Ok(Self::Append(V::read_cfg(buf, cfg)?)),
255            COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
256            e => Err(CodecError::InvalidEnum(e)),
257        }
258    }
259}
260
261impl<K: Array, V: CodecFixed> Read for Fixed<K, V> {
262    type Cfg = <V as Read>::Cfg;
263
264    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
265        at_least(buf, Self::SIZE)?;
266
267        match u8::read(buf)? {
268            UPDATE_CONTEXT => {
269                let key = K::read(buf)?;
270                let value = V::read_cfg(buf, cfg)?;
271                Ok(Self::Update(key, value))
272            }
273            DELETE_CONTEXT => {
274                let key = K::read(buf)?;
275                // Check that the value is all zeroes
276                for _ in 0..V::SIZE {
277                    if u8::read(buf)? != 0 {
278                        return Err(CodecError::Invalid(
279                            "storage::adb::operation::Fixed",
280                            "delete value non-zero",
281                        ));
282                    }
283                }
284                Ok(Self::Delete(key))
285            }
286            COMMIT_FLOOR_CONTEXT => {
287                let floor_loc = u64::read(buf)?;
288                for _ in 0..(Self::SIZE - 1 - u64::SIZE) {
289                    if u8::read(buf)? != 0 {
290                        return Err(CodecError::Invalid(
291                            "storage::adb::operation::Fixed",
292                            "commit value non-zero",
293                        ));
294                    }
295                }
296                Ok(Self::CommitFloor(floor_loc))
297            }
298            e => Err(CodecError::InvalidEnum(e)),
299        }
300    }
301}
302
303impl<K: Array, V: Codec> Read for Variable<K, V> {
304    type Cfg = <V as Read>::Cfg;
305
306    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
307        match u8::read(buf)? {
308            SET_CONTEXT => {
309                let key = K::read(buf)?;
310                let value = V::read_cfg(buf, cfg)?;
311                Ok(Self::Set(key, value))
312            }
313            COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
314            DELETE_CONTEXT => {
315                let key = K::read(buf)?;
316                Ok(Self::Delete(key))
317            }
318            UPDATE_CONTEXT => {
319                let key = K::read(buf)?;
320                let value = V::read_cfg(buf, cfg)?;
321                Ok(Self::Update(key, value))
322            }
323            COMMIT_FLOOR_CONTEXT => {
324                let metadata = Option::<V>::read_cfg(buf, cfg)?;
325                let floor_loc = UInt::read(buf)?;
326                Ok(Self::CommitFloor(metadata, floor_loc.into()))
327            }
328            e => Err(CodecError::InvalidEnum(e)),
329        }
330    }
331}
332
333impl<V: Codec> Display for Keyless<V> {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        match self {
336            Keyless::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
337            Keyless::Commit(value) => {
338                if let Some(value) = value {
339                    write!(f, "[commit {}]", hex(&value.encode()))
340                } else {
341                    write!(f, "[commit]")
342                }
343            }
344        }
345    }
346}
347
348impl<K: Array, V: CodecFixed> Display for Fixed<K, V> {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        match self {
351            Fixed::Delete(key) => write!(f, "[key:{key} <deleted>]"),
352            Fixed::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
353            Fixed::CommitFloor(loc) => write!(f, "[commit with inactivity floor: {loc}]"),
354        }
355    }
356}
357
358impl<K: Array, V: Codec> Display for Variable<K, V> {
359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        match self {
361            Variable::Set(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
362            Variable::Commit(value) => {
363                if let Some(value) = value {
364                    write!(f, "[commit {}]", hex(&value.encode()))
365                } else {
366                    write!(f, "[commit]")
367                }
368            }
369            Variable::Delete(key) => write!(f, "[key:{key} <deleted>]"),
370            Variable::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
371            Variable::CommitFloor(value, loc) => {
372                if let Some(value) = value {
373                    write!(
374                        f,
375                        "[commit {} with inactivity floor: {loc}]",
376                        hex(&value.encode())
377                    )
378                } else {
379                    write!(f, "[commit with inactivity floor: {loc}]")
380                }
381            }
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use commonware_codec::{DecodeExt, Encode};
390    use commonware_utils::sequence::U64;
391
392    #[test]
393    fn test_to_key() {
394        let key = U64::new(1234);
395        let value = U64::new(56789);
396
397        let update_op = Fixed::Update(key.clone(), value.clone());
398        assert_eq!(&key, update_op.key().unwrap());
399
400        let delete_op = Fixed::<U64, U64>::Delete(key.clone());
401        assert_eq!(&key, delete_op.key().unwrap());
402
403        let commit_op = Fixed::<U64, U64>::CommitFloor(42);
404        assert_eq!(None, commit_op.key());
405    }
406
407    #[test]
408    fn test_to_value() {
409        let key = U64::new(1234);
410        let value = U64::new(56789);
411
412        let update_op = Fixed::Update(key.clone(), value.clone());
413        assert_eq!(&value, update_op.value().unwrap());
414
415        let delete_op = Fixed::<U64, U64>::Delete(key.clone());
416        assert_eq!(None, delete_op.value());
417
418        let commit_op = Fixed::<U64, U64>::CommitFloor(42);
419        assert_eq!(None, commit_op.value());
420    }
421
422    #[test]
423    fn test_operation_array_basic() {
424        let key = U64::new(1234);
425        let value = U64::new(56789);
426
427        let update_op = Fixed::Update(key.clone(), value.clone());
428        assert_eq!(&key, update_op.key().unwrap());
429        assert_eq!(&value, update_op.value().unwrap());
430
431        let from = Fixed::<U64, U64>::decode(update_op.encode()).unwrap();
432        assert_eq!(&key, from.key().unwrap());
433        assert_eq!(&value, from.value().unwrap());
434        assert_eq!(update_op, from);
435
436        let key2 = U64::new(42);
437        let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
438        let from = Fixed::<U64, U64>::decode(delete_op.encode()).unwrap();
439        assert_eq!(&key2, from.key().unwrap());
440        assert_eq!(None, from.value());
441        assert_eq!(delete_op, from);
442
443        let commit_op = Fixed::<U64, U64>::CommitFloor(42);
444        let from = Fixed::<U64, U64>::decode(commit_op.encode()).unwrap();
445        assert_eq!(None, from.value());
446        assert!(matches!(from, Fixed::CommitFloor(42)));
447        assert_eq!(commit_op, from);
448
449        // test non-zero byte detection in delete operation
450        let mut invalid = delete_op.encode();
451        invalid[U64::SIZE + 4] = 0xFF;
452        let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
453        assert!(matches!(decoded.unwrap_err(), CodecError::Invalid(_, _)));
454
455        // test invalid context byte detection
456        let mut invalid = delete_op.encode();
457        invalid[0] = 0xFF;
458        let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
459        assert!(matches!(
460            decoded.unwrap_err(),
461            CodecError::InvalidEnum(0xFF)
462        ));
463
464        // test invalid length detection
465        let mut invalid = delete_op.encode().to_vec();
466        invalid.pop();
467        let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
468        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
469    }
470
471    #[test]
472    fn test_operation_array_display() {
473        let key = U64::new(1234);
474        let value = U64::new(56789);
475        let update_op = Fixed::Update(key.clone(), value.clone());
476        assert_eq!(
477            format!("{update_op}"),
478            format!("[key:{key} value:{}]", hex(&value.encode()))
479        );
480
481        let key2 = U64::new(42);
482        let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
483        assert_eq!(format!("{delete_op}"), format!("[key:{key2} <deleted>]"));
484    }
485
486    #[test]
487    fn test_operation_array_codec() {
488        let key = U64::new(1234);
489        let value = U64::new(5678);
490        let update_op = Fixed::Update(key, value);
491
492        let encoded = update_op.encode();
493        assert_eq!(encoded.len(), Fixed::<U64, U64>::SIZE);
494
495        let decoded = Fixed::<U64, U64>::decode(encoded).unwrap();
496        assert_eq!(update_op, decoded);
497    }
498
499    #[test]
500    fn test_keyless_append() {
501        let append_op = Keyless::Append(U64::new(12345));
502
503        let encoded = append_op.encode();
504        assert_eq!(encoded.len(), 1 + U64::SIZE);
505
506        let decoded = Keyless::<U64>::decode(encoded).unwrap();
507        assert_eq!(append_op, decoded);
508        assert_eq!(
509            format!("{append_op}"),
510            format!("[append value:{}]", hex(&U64::new(12345).encode()))
511        );
512    }
513
514    #[test]
515    fn test_keyless_commit() {
516        let metadata = Some(U64::new(12345));
517        let commit_op = Keyless::<U64>::Commit(metadata.clone());
518
519        let encoded = commit_op.encode();
520        assert_eq!(encoded.len(), 1 + metadata.encode_size());
521
522        let decoded = Keyless::<U64>::decode(encoded).unwrap();
523        let Keyless::Commit(metadata_decoded) = decoded else {
524            panic!("expected commit operation");
525        };
526        assert_eq!(metadata, metadata_decoded);
527    }
528
529    #[test]
530    fn test_keyless_invalid_context() {
531        let invalid = vec![0xFF; 1];
532        let decoded = Keyless::<U64>::decode(invalid.as_ref());
533        assert!(matches!(
534            decoded.unwrap_err(),
535            CodecError::InvalidEnum(0xFF)
536        ));
537    }
538}