Add lamport clock

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-11-23 17:34:24 +01:00
parent 0482757ce9
commit 64862b3ab3
No known key found for this signature in database
4 changed files with 59 additions and 9 deletions

39
radicle-crdt/src/clock.rs Normal file
View File

@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
use crate::ord::Max;
/// Lamport clock.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LClock {
counter: Max<u64>,
}
impl LClock {
/// Return the clock value.
pub fn get(&self) -> u64 {
*self.counter.get()
}
/// Increment clock and return new value.
/// Must be called before sending a message.
pub fn tick(&mut self) -> u64 {
self.counter.incr();
self.get()
}
/// Merge clock with another clock, and increment value.
/// Must be called whenever a message is received.
pub fn merge(&mut self, other: Self) {
self.counter.merge(other.counter);
self.counter.incr();
}
}
impl From<u64> for LClock {
fn from(counter: u64) -> Self {
Self {
counter: Max::from(counter),
}
}
}

View File

@ -1,6 +1,7 @@
#![allow(clippy::collapsible_if)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::type_complexity)]
pub mod clock;
pub mod lwwmap;
pub mod lwwreg;
pub mod lwwset;

View File

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::Semilattice;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Max<T>(T);
impl<T> Max<T> {

View File

@ -8,6 +8,7 @@ use radicle::cob::Timestamp;
use radicle::crypto::{PublicKey, Signature, Signer};
use radicle::hash;
use crate::clock::LClock;
use crate::lwwreg::LWWReg;
use crate::lwwset::LWWSet;
@ -31,6 +32,8 @@ pub struct Change {
author: Author,
/// The time at which this change was authored.
timestamp: Timestamp,
/// Lamport clock.
clock: LClock,
}
impl Change {
@ -244,11 +247,12 @@ impl Thread {
#[derive(Default)]
pub struct Actor<G> {
signer: G,
clock: LClock,
}
impl<G: Signer> Actor<G> {
/// Create a new thread.
pub fn thread(&self, title: &str, timestamp: Timestamp) -> Change {
pub fn thread(&mut self, title: &str, timestamp: Timestamp) -> Change {
self.change(
Action::Thread {
title: title.to_owned(),
@ -258,7 +262,7 @@ impl<G: Signer> Actor<G> {
}
/// Create a new comment.
pub fn comment(&self, body: &str, timestamp: Timestamp, parent: ChangeId) -> Change {
pub fn comment(&mut self, body: &str, timestamp: Timestamp, parent: ChangeId) -> Change {
self.change(
Action::Comment {
comment: Comment::new(String::from(body), parent),
@ -268,28 +272,30 @@ impl<G: Signer> Actor<G> {
}
/// Add a tag.
pub fn tag(&self, tag: TagId, timestamp: Timestamp) -> Change {
pub fn tag(&mut self, tag: TagId, timestamp: Timestamp) -> Change {
self.change(Action::Tag { tag }, timestamp)
}
/// Remove a tag.
pub fn untag(&self, tag: TagId, timestamp: Timestamp) -> Change {
pub fn untag(&mut self, tag: TagId, timestamp: Timestamp) -> Change {
self.change(Action::Untag { tag }, timestamp)
}
/// Create a new redaction.
pub fn redact(&self, id: ChangeId, timestamp: Timestamp) -> Change {
pub fn redact(&mut self, id: ChangeId, timestamp: Timestamp) -> Change {
self.change(Action::Redact { id }, timestamp)
}
/// Create a new change.
pub fn change(&self, action: Action, timestamp: Timestamp) -> Change {
pub fn change(&mut self, action: Action, timestamp: Timestamp) -> Change {
let author = *self.signer.public_key();
let clock = self.clock.tick();
Change {
action,
author,
timestamp,
clock: clock.into(),
}
}
@ -397,15 +403,18 @@ mod tests {
let mut changes = Vec::new();
let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new());
let mut clock = LClock::default();
let author = PublicKey::from([0; 32]);
for action in gen.take(g.size().min(8)) {
let timestamp = Timestamp::now() + rng.u64(0..3);
clock.tick();
changes.push(Change {
action,
author,
timestamp,
clock,
});
}
@ -420,7 +429,7 @@ mod tests {
#[quickcheck]
fn prop_invariants(log: Changes<3>) {
let bob = Actor::<MockSigner>::default();
let mut bob = Actor::<MockSigner>::default();
let b0 = bob.thread("The Thread", Timestamp::now());
let t = Thread::new(b0);
let [p1, p2, p3] = log.permutations;
@ -440,8 +449,8 @@ mod tests {
#[test]
fn test_invariants() {
let alice = Actor::<MockSigner>::default();
let bob = Actor::<MockSigner>::default();
let mut alice = Actor::<MockSigner>::default();
let mut bob = Actor::<MockSigner>::default();
let time = Timestamp::now();
let b0 = bob.thread("Dinner Ingredients", time);