1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
//! # Understanding Convergent Replicated Data Types //! //! I wanted to understand CRDTs more, so I put together this Rust library for state-based CRDTs a.k.a. convergent replicated data types a.k.a. CvRDTs. //! It aims to present an explicit (with strong types, etc.) and unified description of the CvRDTs presented in the _wonderful_ paper [A comprehensive study of Convergent and Commutative Replicated Data Types](https://hal.inria.fr/inria-00555588/). //! //! ## Do not use this! //! //! This code is solely for my own edification and is _not_ meant for production use. //! There are already much better options for usable CRDTs in Rust; see the [`rust-crdt`](https://github.com/rust-crdt/rust-crdt) project. //! //! ## What makes a CvRDT? //! //! Quoting the [Wikipedia article on CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type), //! > CvRDTs send their full local state to other replicas, where the states are merged by a function which must be commutative, associative, and idempotent. //! //! So suppose we've just written a brand new data type, and we'd like to demonstrate it's a CvRDT. //! Suppose further that _x_, _y_, and _z_ are any arbitrary members of our data type, and that our data type has a merge function called _merge_; for our type to be a CvRDT, we need the following three things to be true for **any** values of _x_, _y_, and _z_: //! 1. _merge(x, y) = merge(y, x)_ //! 2. _merge(x, merge(y, z)) = merge(merge(x, y), z)_ //! 3. _merge(merge(x, y), y) = merge(x, y)_ //! //! [Wikipedia](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) continues, //! > The merge function provides a join for any pair of replica states, so the set of all states forms a semilattice. //! //! Our merge function _merge_ induces a partial order on all the elements of our type, akin to Rust's [`PartialOrd` trait](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html), where _x ≤ y_ if _merge(x, y) = y_. //! Since for any two elements _x_ and _y_, both _x ≤ merge(x, y)_ and _y ≤ merge(x, y)_, and _merge(x, y)_ is the "smallest" such elment for which this occurs, it is the least upper bound (or **join**) of _x_ and _y_. //! //! [The article](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) goes on //! > The update function must monotonically increase the internal state, according to the same partial order rules as the semilattice. //! //! Suppose our type has an update function which we'll call _add_. //! For any element _x_ and update value _u_, we need _x ≤ update(x, u)_; that is, we need _merge(x, update(x, u)) = update(x, u)_. //! //! Many types of CvRDT—in this library especially—are grow-only; they can add new elements/values/etc., but once added, these cannot be removed. //! Some types allow removal as well; if our type does so, we **must** ensure that removal maintains the partial order given by our _merge_ function. //! That is, we need the internal state to increase monotonically (as Wikipedia says in the above quote) even in the face of removals. //! Suppose our type has a function //! //! Ideally we'd check these requirements for **every** possible configuration of our new CvRDT implementation. //! This can be impossible to do via brute force, as the number of states to check can quickly get prohibitively large. //! See the [penultimate section](#how-cvrdt-exposition-verifies-properties) for how `cvrdt-exposition` verifies these properties. //! //! ## Examples //! //! Per the [Wikipedia article](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type), consider a one-way boolean flag that, once true, can never revert to false: //! ``` //! use cvrdt_exposition::{Grow, OneWayBoolean}; //! let x = OneWayBoolean::new(false); //! let mut y = OneWayBoolean::new(false); //! y.add(()); //! assert_eq!(y.payload(), true); //! assert_eq!(y.merge(&x).payload(), true); //! ``` //! //! As the internal state of a `OneWayBoolean` is only a single boolean value, we could verify the implementation fulfills the CvRDT requirements by hand (though I'd rather get my computer to do it! see below). //! //! ## How `cvrdt-exposition` verifies properties //! //! In the absence of using formal methods like [TLA+](https://learntla.com/introduction/) (see also Hillel Wayne's [excellent book!](https://learntla.com/book/)), we resort to property-based testing via the [`proptest` crate](https://crates.io/crates/proptest). //! This excellent crate is listed as a `dev-dependency` in [`cvrdt-exposition`'s `Cargo.tml` file](https://github.com/genos/cvrdt-exposition/blob/main/Cargo.toml), so if you can just use the stuff in this library (although [you shouldn't!](do-not-use-this)) without pulling in an extra dependency. //! That said, I highly recommend learning to use `proptest`, [`quickcheck`](https://crates.io/crates/quickcheck), or some other [property testing framework](https://crates.io/search?q=property%20testing) for Rust. //! //! In the `cfg(test)`-only [`properties` module](https://github.com/genos/cvrdt/blob/main/src/properties.rs), `cvrdt-exposition` defines macros for automating checking CvRDT properties. //! For instance, given a function `arb_cvrdt2` that yields an arbitrary pair of elements of our CvRDT type, `proptest`'s `proptest!` macro allows us to test that our CvRDT's merge function is commutative via: //! //! ```ignore //! proptest! { //! #[test] //! fn merge_commutative((x, y) in $arb_cvrdt2()) { //! prop_assert_eq!( //! Grow::payload(&Grow::merge(&x, &y)), //! Grow::payload(&Grow::merge(&y, &x)) //! ); //! } //! }; //! ``` //! //! Note that the above code uses the `cvrdt-exposition` nomenclature `Grow`, which we use to distinguish CvRDTs that are grow-only from those that can also remove elements. //! See the [traits documentation](traits/index.html) for more. //! //! ## References //! //! - [A comprehensive study of Convergent and Commutative Replicated Data Types](https://hal.inria.fr/inria-00555588/) //! - Wikipedia article on [Conflict-free replicated data type](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) //! - [`rust-crdt`](https://github.com/rust-crdt/rust-crdt) //! - [`meangirls`](https://github.com/aphyr/meangirls) //! - [The `proptest` book](https://altsysrq.github.io/proptest-book/intro.html) #![forbid(unsafe_code)] #![forbid(missing_docs)] /// Our two traits defining CvRDTs pub mod traits; /// Grow-Only Counter pub mod g_counter; /// Grow-Only Set pub mod g_set; /// Last-Writer-Wins Register pub mod lww_register; /// The simplest CvRDT example: a boolean flag that, once true, can never revert to false pub mod one_way_boolean; /// Positive-Negative Counter pub mod pn_counter; /// Two-Phase Set pub mod two_phase_set; /// Top-level re-exports for CRDT structures and traits pub use crate::{ g_counter::GCounter, g_set::GSet, lww_register::LWWRegister, one_way_boolean::OneWayBoolean, pn_counter::PNCounter, traits::{Grow, Shrink}, two_phase_set::TwoPhaseSet, }; /// PBT for CvRDT properties #[cfg(test)] pub mod properties;