commonware_storage/adb/operation/fixed/
unordered.rs

1use crate::{
2    adb::operation::{self, fixed::FixedOperation},
3    mmr::Location,
4};
5use bytes::{Buf, BufMut};
6use commonware_codec::{
7    util::at_least, CodecFixed, Error as CodecError, FixedSize, Read, ReadExt as _, Write,
8};
9use commonware_utils::{hex, Array};
10use core::fmt::Display;
11
12/// An operation applied to an authenticated database with a fixed size value.
13#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
14pub enum Operation<K: Array, V: CodecFixed> {
15    /// Indicates the key no longer has a value.
16    Delete(K),
17
18    /// Indicates the key now has the wrapped value.
19    Update(K, V),
20
21    /// Indicates all prior operations are no longer subject to rollback, and the floor on inactive
22    /// operations has been raised to the wrapped value.
23    CommitFloor(Location),
24}
25
26impl<K: Array, V: CodecFixed> Operation<K, V> {
27    // A compile-time assertion that operation's array size is large enough to handle the commit
28    // operation, which requires 9 bytes.
29    const _MIN_OPERATION_LEN: usize = 9;
30
31    /// Asserts that the size of `Self` is greater than the minimum operation size.
32    #[inline(always)]
33    const fn assert_valid_size() {
34        assert!(
35            Self::SIZE >= Self::_MIN_OPERATION_LEN,
36            "array size too small for commit op"
37        );
38    }
39}
40
41impl<K: Array, V: CodecFixed> FixedSize for Operation<K, V> {
42    const SIZE: usize = u8::SIZE + K::SIZE + V::SIZE;
43}
44
45impl<K: Array, V: CodecFixed<Cfg = ()>> FixedOperation for Operation<K, V> {
46    type Key = K;
47    type Value = V;
48
49    fn commit_floor(&self) -> Option<Location> {
50        match self {
51            Self::CommitFloor(loc) => Some(*loc),
52            _ => None,
53        }
54    }
55
56    fn key(&self) -> Option<&Self::Key> {
57        // TODO: Re-evaluate assertion placement after `generic_const_exprs` is stable.
58        const {
59            Self::assert_valid_size();
60        }
61
62        match self {
63            Self::Delete(key) => Some(key),
64            Self::Update(key, _) => Some(key),
65            Self::CommitFloor(_) => None,
66        }
67    }
68
69    fn value(&self) -> Option<&Self::Value> {
70        // TODO: Re-evaluate assertion placement after `generic_const_exprs` is stable.
71        const {
72            Self::assert_valid_size();
73        }
74
75        match self {
76            Self::Delete(_) => None,
77            Self::Update(_, value) => Some(value),
78            Self::CommitFloor(_) => None,
79        }
80    }
81
82    fn into_value(self) -> Option<Self::Value> {
83        // TODO: Re-evaluate assertion placement after `generic_const_exprs` is stable.
84        const {
85            Self::assert_valid_size();
86        }
87
88        match self {
89            Self::Delete(_) => None,
90            Self::Update(_, value) => Some(value),
91            Self::CommitFloor(_) => None,
92        }
93    }
94}
95
96impl<K: Array, V: CodecFixed> Write for Operation<K, V> {
97    fn write(&self, buf: &mut impl BufMut) {
98        match &self {
99            Self::Delete(k) => {
100                operation::DELETE_CONTEXT.write(buf);
101                k.write(buf);
102                // Pad with 0 up to [Self::SIZE]
103                buf.put_bytes(0, V::SIZE);
104            }
105            Self::Update(k, v) => {
106                operation::UPDATE_CONTEXT.write(buf);
107                k.write(buf);
108                v.write(buf);
109            }
110            Self::CommitFloor(floor_loc) => {
111                operation::COMMIT_FLOOR_CONTEXT.write(buf);
112                buf.put_slice(&floor_loc.to_be_bytes());
113                // Pad with 0 up to [Self::SIZE]
114                buf.put_bytes(0, Self::SIZE - 1 - u64::SIZE);
115            }
116        }
117    }
118}
119
120impl<K: Array, V: CodecFixed> Read for Operation<K, V> {
121    type Cfg = <V as Read>::Cfg;
122
123    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
124        at_least(buf, Self::SIZE)?;
125
126        match u8::read(buf)? {
127            operation::UPDATE_CONTEXT => {
128                let key = K::read(buf)?;
129                let value = V::read_cfg(buf, cfg)?;
130                Ok(Self::Update(key, value))
131            }
132            operation::DELETE_CONTEXT => {
133                let key = K::read(buf)?;
134                // Check that the value is all zeroes
135                for _ in 0..V::SIZE {
136                    if u8::read(buf)? != 0 {
137                        return Err(CodecError::Invalid(
138                            "storage::adb::operation::fixed::unordered::Operation",
139                            "delete value non-zero",
140                        ));
141                    }
142                }
143                Ok(Self::Delete(key))
144            }
145            operation::COMMIT_FLOOR_CONTEXT => {
146                let floor_loc = u64::read(buf)?;
147                for _ in 0..(Self::SIZE - 1 - u64::SIZE) {
148                    if u8::read(buf)? != 0 {
149                        return Err(CodecError::Invalid(
150                            "storage::adb::operation::fixed::unordered::Operation",
151                            "commit value non-zero",
152                        ));
153                    }
154                }
155                let floor_loc = Location::new(floor_loc).ok_or_else(|| {
156                    CodecError::Invalid(
157                        "storage::adb::operation::fixed::unordered::Operation",
158                        "commit floor location overflow",
159                    )
160                })?;
161                Ok(Self::CommitFloor(floor_loc))
162            }
163            e => Err(CodecError::InvalidEnum(e)),
164        }
165    }
166}
167
168impl<K: Array, V: CodecFixed> Display for Operation<K, V> {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        match self {
171            Self::Delete(key) => write!(f, "[key:{key} <deleted>]"),
172            Self::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
173            Self::CommitFloor(loc) => write!(f, "[commit with inactivity floor: {loc}]"),
174        }
175    }
176}