Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
crdt: Include timestamp in `Change`
Alexis Sellier committed 3 years ago
commit 64ce15dbe236a16624c0a3af19f06c88774cf7a3
parent d061afdab11ab6135c860f5288ad9d80faf1af17
9 files changed +103 -79
modified radicle-cli/src/terminal/format.rs
@@ -2,10 +2,9 @@ use std::{fmt, time};

pub use dialoguer::console::style;

-
use radicle::cob::Timestamp;
+
use radicle::cob::{ObjectId, Timestamp};
use radicle::node::NodeId;
use radicle::profile::Profile;
-
use radicle_cob::ObjectId;

use crate::terminal as term;

modified radicle-cob/src/history.rs
@@ -89,7 +89,7 @@ impl History {

    /// Get the current history timestamp.
    /// This is the latest timestamp of any tip.
-
    pub fn timestamp(&self) -> Clock {
+
    pub fn timestamp(&self) -> Timestamp {
        self.graph
            .externals(petgraph::Direction::Outgoing)
            .map(|n| self.graph[n].timestamp())
modified radicle-crdt/src/change.rs
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use radicle_crypto::{PublicKey, Signature, Signer};
use serde::{Deserialize, Serialize};

+
use crate::clock;
use crate::clock::Lamport;

/// Identifies a change.
@@ -21,6 +22,8 @@ pub struct Change<A> {
    pub author: ActorId,
    /// Lamport clock.
    pub clock: Lamport,
+
    /// Timestamp of this change.
+
    pub timestamp: clock::Physical,
}

impl<A> Change<A> {
@@ -101,10 +104,12 @@ impl<G: Signer, A: Clone + Serialize> Actor<G, A> {
    pub fn change(&mut self, action: A) -> Change<A> {
        let author = *self.signer.public_key();
        let clock = self.clock;
+
        let timestamp = clock::Physical::now();
        let change = Change {
            action,
            author,
            clock,
+
            timestamp,
        };
        self.changes.insert((self.clock, author), change.clone());
        self.clock.tick();
modified radicle-crdt/src/clock.rs
@@ -1,3 +1,6 @@
+
use std::time::SystemTime;
+
use std::time::UNIX_EPOCH;
+

use num_traits::Bounded;
use serde::{Deserialize, Serialize};

@@ -54,3 +57,59 @@ impl Bounded for Lamport {
        Self::from(u64::max_value())
    }
}
+

+
/// Physical clock. Tracks real-time by the second.
+
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
+
#[serde(transparent)]
+
pub struct Physical {
+
    seconds: u64,
+
}
+

+
impl Physical {
+
    pub fn new(seconds: u64) -> Self {
+
        Self { seconds }
+
    }
+

+
    pub fn now() -> Self {
+
        #[allow(clippy::unwrap_used)] // Safe because Unix was already invented!
+
        let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+

+
        Self {
+
            seconds: duration.as_secs(),
+
        }
+
    }
+

+
    pub fn as_secs(&self) -> u64 {
+
        self.seconds
+
    }
+
}
+

+
impl From<u64> for Physical {
+
    fn from(seconds: u64) -> Self {
+
        Self { seconds }
+
    }
+
}
+

+
impl std::ops::Add<u64> for Physical {
+
    type Output = Self;
+

+
    fn add(self, rhs: u64) -> Self::Output {
+
        Self {
+
            seconds: self.seconds + rhs,
+
        }
+
    }
+
}
+

+
impl Bounded for Physical {
+
    fn min_value() -> Self {
+
        Self {
+
            seconds: u64::min_value(),
+
        }
+
    }
+

+
    fn max_value() -> Self {
+
        Self {
+
            seconds: u64::max_value(),
+
        }
+
    }
+
}
modified radicle/src/cob.rs
@@ -4,6 +4,8 @@ pub mod patch;
pub mod store;
pub mod thread;

+
pub use radicle_crdt::clock::Physical as Timestamp;
+

pub use cob::{
    identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History,
    ObjectId, TypeName, Update,
modified radicle/src/cob/common.rs
@@ -1,12 +1,12 @@
use std::fmt;
use std::str::FromStr;
-
use std::time::{SystemTime, UNIX_EPOCH};

-
use num_traits::Bounded;
use serde::{Deserialize, Serialize};

use crate::prelude::*;

+
pub use radicle_crdt::clock::Physical as Timestamp;
+

/// Author.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Author {
@@ -23,55 +23,6 @@ impl Author {
    }
}

-
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
-
#[serde(transparent)]
-
pub struct Timestamp {
-
    seconds: u64,
-
}
-

-
impl Timestamp {
-
    pub fn new(seconds: u64) -> Self {
-
        Self { seconds }
-
    }
-

-
    pub fn now() -> Self {
-
        #[allow(clippy::unwrap_used)] // Safe because Unix was already invented!
-
        let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-

-
        Self {
-
            seconds: duration.as_secs(),
-
        }
-
    }
-

-
    pub fn as_secs(&self) -> u64 {
-
        self.seconds
-
    }
-
}
-

-
impl std::ops::Add<u64> for Timestamp {
-
    type Output = Self;
-

-
    fn add(self, rhs: u64) -> Self::Output {
-
        Self {
-
            seconds: self.seconds + rhs,
-
        }
-
    }
-
}
-

-
impl Bounded for Timestamp {
-
    fn min_value() -> Self {
-
        Self {
-
            seconds: u64::min_value(),
-
        }
-
    }
-

-
    fn max_value() -> Self {
-
        Self {
-
            seconds: u64::max_value(),
-
        }
-
    }
-
}
-

#[derive(thiserror::Error, Debug)]
pub enum ReactionError {
    #[error("invalid reaction")]
modified radicle/src/cob/issue.rs
@@ -113,6 +113,7 @@ impl store::FromHistory for Issue {
                    action,
                    author: *entry.actor(),
                    clock: entry.clock().into(),
+
                    timestamp: entry.timestamp().into(),
                }) {
                    log::warn!("Error applying change to issue state: {err}");
                    return ControlFlow::Break(acc);
@@ -168,6 +169,7 @@ impl Issue {
                    action,
                    author: change.author,
                    clock: change.clock,
+
                    timestamp: change.timestamp,
                }]);
            }
        }
@@ -275,16 +277,17 @@ impl<'a, 'g> IssueMut<'a, 'g> {
            .store
            .update(self.id, msg, action.clone(), signer)
            .map_err(Error::Store)?;
-
        let clock = cob.history().clock();
-

+
        let clock = cob.history().clock().into();
+
        let timestamp = cob.history().timestamp().into();
        let change = Change {
-
            author: *signer.public_key(),
            action,
-
            clock: clock.into(),
+
            author: *signer.public_key(),
+
            clock,
+
            timestamp,
        };
        self.issue.apply(change)?;

-
        Ok((clock.into(), *signer.public_key()))
+
        Ok((clock, *signer.public_key()))
    }
}

modified radicle/src/cob/patch.rs
@@ -13,11 +13,10 @@ use radicle_crdt::{ActorId, ChangeId, LWWReg, LWWSet, Max, Redactable, Semilatti
use serde::{Deserialize, Serialize};
use thiserror::Error;

-
use crate::cob::common::{Author, Tag};
+
use crate::cob::common::{Author, Tag, Timestamp};
use crate::cob::thread;
use crate::cob::thread::CommentId;
use crate::cob::thread::Thread;
-
use crate::cob::Timestamp;
use crate::cob::{store, ObjectId, TypeName};
use crate::crypto::{PublicKey, Signer};
use crate::git;
@@ -31,6 +30,7 @@ pub use clock::Lamport as Clock;
pub static TYPENAME: Lazy<TypeName> =
    Lazy::new(|| FromStr::from_str("xyz.radicle.patch").expect("type name is valid"));

+
/// Patch change.
pub type Change = crdt::Change<Action>;

/// Identifier for a patch.
@@ -240,8 +240,7 @@ impl Patch {
    pub fn apply_one(&mut self, change: Change) -> Result<(), ApplyError> {
        let id = change.id();
        let author = Author::new(change.author);
-
        // FIXME(cloudhead): Use commit timestamp.
-
        let timestamp = Timestamp::default();
+
        let timestamp = change.timestamp;

        match change.action {
            Action::Edit {
@@ -318,6 +317,7 @@ impl Patch {
                        action,
                        author: change.author,
                        clock: change.clock,
+
                        timestamp,
                    }]);
                } else {
                    return Err(ApplyError::Missing(revision));
@@ -344,6 +344,7 @@ impl store::FromHistory for Patch {
                    action,
                    author: *entry.actor(),
                    clock: entry.clock().into(),
+
                    timestamp: entry.timestamp().into(),
                }]) {
                    log::warn!("Error applying change to patch state: {err}");
                    return ControlFlow::Break(acc);
@@ -669,20 +670,21 @@ impl<'a, 'g> PatchMut<'a, 'g> {
        action: Action,
        signer: &G,
    ) -> Result<ChangeId, Error> {
-
        let change = Change {
-
            author: *signer.public_key(),
-
            action: action.clone(),
-
            clock: self.clock.tick(),
-
        };
-
        self.patch.apply([change])?;
-

        let cob = self
            .store
-
            .update(self.id, msg, action, signer)
+
            .update(self.id, msg, action.clone(), signer)
            .map_err(Error::Store)?;
-
        let clock = cob.history().clock();
+
        let clock = cob.history().clock().into();
+
        let timestamp = cob.history().timestamp().into();
+
        let change = Change {
+
            action,
+
            author: *signer.public_key(),
+
            clock,
+
            timestamp,
+
        };
+
        self.patch.apply_one(change)?;

-
        Ok((clock.into(), *signer.public_key()))
+
        Ok((clock, *signer.public_key()))
    }
}

@@ -868,12 +870,14 @@ mod test {

            let mut changes = Vec::new();
            let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new());
+
            let timestamp = Timestamp::now() + rng.u64(..60);

            for (clock, action) in gen.take(g.size()) {
                changes.push(Change {
                    action,
                    author,
                    clock,
+
                    timestamp,
                });
            }

modified radicle/src/cob/thread.rs
@@ -8,9 +8,9 @@ use once_cell::sync::Lazy;
use radicle_crdt as crdt;
use serde::{Deserialize, Serialize};

-
use crate::cob::common::{Reaction, Tag};
+
use crate::cob::common::{Reaction, Tag, Timestamp};
use crate::cob::store;
-
use crate::cob::{History, Timestamp, TypeName};
+
use crate::cob::{History, TypeName};
use crate::crypto::Signer;

use crdt::clock::Lamport;
@@ -114,6 +114,7 @@ impl store::FromHistory for Thread {
                    action,
                    author: *entry.actor(),
                    clock: entry.clock().into(),
+
                    timestamp: entry.timestamp().into(),
                }]);
                ControlFlow::Continue(acc)
            } else {
@@ -187,15 +188,13 @@ impl Thread {
    }

    pub fn apply(&mut self, changes: impl IntoIterator<Item = Change<Action>>) {
-
        // FIXME(cloudhead): Use commit timestamp.
-
        let timestamp = Timestamp::default();
-

        for change in changes.into_iter() {
            let id = change.id();

            match change.action {
                Action::Comment { body, reply_to } => {
-
                    let present = Redactable::Present(Comment::new(body, reply_to, timestamp));
+
                    let present =
+
                        Redactable::Present(Comment::new(body, reply_to, change.timestamp));

                    match self.comments.entry(id) {
                        Entry::Vacant(e) => {
@@ -436,12 +435,14 @@ mod tests {

            let mut changes = Vec::new();
            let mut permutations: [Vec<Change<Action>>; N] = array::from_fn(|_| Vec::new());
+
            let timestamp = Timestamp::now() + rng.u64(..60);

            for (clock, action) in gen.take(g.size().min(8)) {
                changes.push(Change {
                    action,
                    author,
                    clock,
+
                    timestamp,
                });
            }