crdts/
lwwreg.rs

1use std::{error, fmt};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CmRDT, CvRDT};
6
7/// `LWWReg` is a simple CRDT that contains an arbitrary value
8/// along with an `Ord` that tracks causality. It is the responsibility
9/// of the user to guarantee that the source of the causal element
10/// is monotonic. Don't use timestamps unless you are comfortable
11/// with divergence.
12///
13/// `M` is a marker. It must grow monotonically *and* must be globally unique
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct LWWReg<V, M> {
16    /// `val` is the opaque element contained within this CRDT
17    pub val: V,
18    /// `marker` should be a monotonic value associated with this val
19    pub marker: M,
20}
21
22impl<V: Default, M: Default> Default for LWWReg<V, M> {
23    fn default() -> Self {
24        Self {
25            val: V::default(),
26            marker: M::default(),
27        }
28    }
29}
30
31/// The Type of validation errors that may occur for an LWWReg.
32#[derive(Debug, PartialEq)]
33pub enum Validation {
34    /// A conflicting change to a CRDT is witnessed by a dot that already exists.
35    ConflictingMarker,
36}
37
38impl error::Error for Validation {
39    fn description(&self) -> &str {
40        match self {
41            Validation::ConflictingMarker => {
42                "A marker must be used exactly once, re-using the same marker breaks associativity"
43            }
44        }
45    }
46}
47
48impl fmt::Display for Validation {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        write!(f, "{:?}", self)
51    }
52}
53
54impl<V: PartialEq, M: Ord> CvRDT for LWWReg<V, M> {
55    type Validation = Validation;
56
57    /// Validates whether a merge is safe to perfom
58    ///
59    /// Returns an error if the marker is identical but the
60    /// contained element is different.
61    /// ```
62    /// use crdts::{lwwreg, LWWReg, CvRDT};
63    /// let mut l1 = LWWReg { val: 1, marker: 2 };
64    /// let l2 = LWWReg { val: 3, marker: 2 };
65    /// // errors!
66    /// assert_eq!(l1.validate_merge(&l2), Err(lwwreg::Validation::ConflictingMarker));
67    /// ```
68    fn validate_merge(&self, other: &Self) -> Result<(), Self::Validation> {
69        self.validate_update(&other.val, &other.marker)
70    }
71
72    /// Combines two `LWWReg` instances according to the marker that
73    /// tracks causality.
74    fn merge(&mut self, LWWReg { val, marker }: Self) {
75        self.update(val, marker)
76    }
77}
78
79impl<V: PartialEq, M: Ord> CmRDT for LWWReg<V, M> {
80    // LWWReg's are small enough that we can replicate
81    // the entire state as an Op
82    type Op = Self;
83    type Validation = Validation;
84
85    fn validate_op(&self, op: &Self::Op) -> Result<(), Self::Validation> {
86        self.validate_update(&op.val, &op.marker)
87    }
88
89    fn apply(&mut self, op: Self::Op) {
90        self.merge(op)
91    }
92}
93
94impl<V: PartialEq, M: Ord> LWWReg<V, M> {
95    /// Updates value witnessed by the given marker.
96    ///
97    /// ```
98    /// use crdts::LWWReg;
99    /// let mut reg = LWWReg { val: 1, marker: 2 };
100    ///
101    /// // updating with a smaller marker is a no-op
102    /// reg.update(2, 1);
103    /// assert_eq!(reg.val, 1);
104    ///
105    /// // updating with larger marker succeeds
106    /// reg.update(2, 3);
107    /// assert_eq!(reg, LWWReg { val: 2, marker: 3 });
108    /// ```
109    pub fn update(&mut self, val: V, marker: M) {
110        if self.marker < marker {
111            self.val = val;
112            self.marker = marker;
113        }
114    }
115
116    /// An update is invalid if the marker is exactly the same as
117    /// the current marker BUT the value is different:
118    /// ```
119    /// use crdts::{lwwreg, LWWReg};
120    /// let mut reg = LWWReg { val: 1, marker: 2 };
121    ///
122    /// // updating with a smaller marker is a no-op
123    /// assert_eq!(reg.validate_update(&32, &2), Err(lwwreg::Validation::ConflictingMarker));
124    /// ```
125    pub fn validate_update(&self, val: &V, marker: &M) -> Result<(), Validation> {
126        if &self.marker == marker && val != &self.val {
127            Err(Validation::ConflictingMarker)
128        } else {
129            Ok(())
130        }
131    }
132}
133
134#[cfg(test)]
135mod test {
136    use super::*;
137
138    #[test]
139    fn test_default() {
140        let reg = LWWReg::default();
141        assert_eq!(reg, LWWReg { val: "", marker: 0 });
142    }
143
144    #[test]
145    fn test_update() {
146        let mut reg = LWWReg {
147            val: 123,
148            marker: 0,
149        };
150
151        // normal update: new marker is a descended of current marker
152        // EXPECTED: success, the val and marker are update
153        reg.update(32, 2);
154        assert_eq!(reg, LWWReg { val: 32, marker: 2 });
155
156        // stale update: new marker is an ancester of the current marker
157        // EXPECTED: succes, no-op
158        reg.update(57, 1);
159        assert_eq!(reg, LWWReg { val: 32, marker: 2 });
160
161        // redundant update: new marker and val is same as of the current state
162        // EXPECTED: success, no-op
163        reg.update(32, 2);
164        assert_eq!(reg, LWWReg { val: 32, marker: 2 });
165
166        // bad update: new marker same as of the current marker but not value
167        // EXPECTED: error
168        assert_eq!(
169            reg.validate_update(&4000, &2),
170            Err(Validation::ConflictingMarker)
171        );
172
173        // Applying the update despite the validation error is a no-op
174        reg.update(4000, 2);
175        assert_eq!(reg, LWWReg { val: 32, marker: 2 });
176    }
177
178    #[cfg(feature = "quickcheck")]
179    mod prop_tests {
180        use super::*;
181        use quickcheck::TestResult;
182        use quickcheck_macros::quickcheck;
183
184        #[quickcheck]
185        fn prop_associative(
186            r1_prim: (u8, u16),
187            r2_prim: (u8, u16),
188            r3_prim: (u8, u16),
189        ) -> TestResult {
190            let mut r1 = build_from_prim(r1_prim);
191            let mut r2 = build_from_prim(r2_prim);
192            let r3 = build_from_prim(r3_prim);
193
194            let has_conflicting_marker = (r1.marker == r2.marker && r1.val != r2.val)
195                || (r1.marker == r3.marker && r1.val != r3.val)
196                || (r2.marker == r3.marker && r2.val != r3.val);
197
198            if has_conflicting_marker {
199                return TestResult::discard();
200            }
201
202            let mut r1_snapshot = r1.clone();
203
204            // (r1 ^ r2) ^ r3
205            r1.merge(r2.clone());
206            r1.merge(r3.clone());
207
208            // r1 ^ (r2 ^ r3)
209            r2.merge(r3);
210            r1_snapshot.merge(r2);
211
212            // (r1 ^ r2) ^ r3 = r1 ^ (r2 ^ r3)
213            TestResult::from_bool(r1 == r1_snapshot)
214        }
215
216        #[quickcheck]
217        fn prop_commutative(r1_prim: (u8, u16), r2_prim: (u8, u16)) -> TestResult {
218            let mut r1 = build_from_prim(r1_prim);
219            let mut r2 = build_from_prim(r2_prim);
220
221            if r1.marker == r2.marker && r1.val != r2.val {
222                return TestResult::discard();
223            }
224            let r1_snapshot = r1.clone();
225
226            // r1 ^ r2
227            r1.merge(r2.clone());
228
229            // r2 ^ r1
230            r2.merge(r1_snapshot);
231
232            // r1 ^ r2 = r2 ^ r1
233            TestResult::from_bool(r1 == r2)
234        }
235
236        #[quickcheck]
237        fn prop_idempotent(r_prim: (u8, u16)) -> bool {
238            let mut r = build_from_prim(r_prim);
239            let r_snapshot = r.clone();
240
241            // r ^ r
242            r.merge(r_snapshot.clone());
243            // r ^ r = r
244            r == r_snapshot
245        }
246
247        fn build_from_prim(prim: (u8, u16)) -> LWWReg<u8, (u16, u8)> {
248            // we make the marker a tuple so that we avoid conflicts
249            LWWReg {
250                val: prim.0,
251                marker: (prim.1, prim.0),
252            }
253        }
254    }
255}