Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
Small refactoring of `Canonical`
Merged fintohaps opened 10 months ago

The Canonical type is generally constructed with the threshold, needed for the quorum computation, in scope. To make it easier to use and encapsulate its functionality, it now holds the threshold as an additional field.

Changes for the Canonical type:

  • Reorder arguments so that delegates and threshold are grouped after refname
  • Add Canonical::is_empty to detect when Canonical was constructed with no tips
  • Consume Canonical when calling Canonical::quorum to indicate that it should be the final step
3 files changed +33 -15 b614167b ab62dce6
modified crates/radicle-remote-helper/src/push.rs
@@ -280,6 +280,7 @@ pub fn run(
                                stored,
                                &project,
                                identity.delegates().as_ref(),
+
                                identity.threshold(),
                            )?;
                            let converges = canonical::converges(
                                canonical
@@ -292,7 +293,7 @@ pub fn run(
                                canonical.modify_vote(me, head.into());
                            }

-
                            match canonical.quorum(identity.threshold(), &working) {
+
                            match canonical.quorum(&working) {
                                Ok(canonical_oid) => {
                                    // Canonical head is an ancestor of head.
                                    let is_ff = head == *canonical_oid
modified crates/radicle/src/git/canonical.rs
@@ -21,8 +21,10 @@ use super::{lit, Oid, Qualified};
///
/// `Canonical` can then be used for performing calculations about the
/// canonicity of the reference, most importantly the [`Canonical::quorum`].
+
#[derive(Debug)]
pub struct Canonical {
    tips: BTreeMap<Did, Oid>,
+
    threshold: usize,
}

/// Error that can occur when calculation the [`Canonical::quorum`].
@@ -90,14 +92,16 @@ impl Canonical {
        repo: &S,
        project: &Project,
        delegates: &NonEmpty<Did>,
+
        threshold: usize,
    ) -> Result<Self, raw::Error>
    where
        S: ReadRepository,
    {
        Self::reference(
            repo,
-
            delegates,
            &lit::refs_heads(project.default_branch()).into(),
+
            delegates,
+
            threshold,
        )
    }

@@ -105,35 +109,44 @@ impl Canonical {
    /// the reference `name`.
    pub fn reference<S>(
        repo: &S,
+
        refname: &Qualified,
        delegates: &NonEmpty<Did>,
-
        name: &Qualified,
+
        threshold: usize,
    ) -> Result<Self, raw::Error>
    where
        S: ReadRepository,
    {
        let mut tips = BTreeMap::new();
        for delegate in delegates.iter() {
-
            match repo.reference_oid(delegate, name) {
+
            match repo.reference_oid(delegate, refname) {
                Ok(tip) => {
                    tips.insert(*delegate, tip);
                }
                Err(e) if super::ext::is_not_found_err(&e) => {
                    log::warn!(
                        target: "radicle",
-
                        "Missing `refs/namespaces/{}/{name}` while calculating the canonical reference",
+
                        "Missing `refs/namespaces/{}/{refname}` while calculating the canonical reference",
                        delegate.as_key()
                    );
                }
                Err(e) => return Err(e),
            }
        }
-
        Ok(Canonical { tips })
+
        Ok(Canonical { tips, threshold })
    }

    /// Return the set of [`Did`]s and their [`Oid`] tip.
    pub fn tips(&self) -> impl Iterator<Item = (&Did, &Oid)> {
        self.tips.iter()
    }
+

+
    /// Returns `true` is there were no tips found for any of the delegates for
+
    /// the given reference.
+
    ///
+
    /// N.b. this may be the case when a new reference is being created.
+
    pub fn is_empty(&self) -> bool {
+
        self.tips.is_empty()
+
    }
}

/// Check that a given `target` converges with any of the provided `tips`.
@@ -171,7 +184,7 @@ impl Canonical {
    ///
    /// Also returns an error if `heads` is empty or `threshold` cannot be
    /// satisified with the number of heads given.
-
    pub fn quorum(&self, threshold: usize, repo: &raw::Repository) -> Result<Oid, QuorumError> {
+
    pub fn quorum(self, repo: &raw::Repository) -> Result<Oid, QuorumError> {
        let mut candidates = BTreeMap::<_, usize>::new();

        // Build a list of candidate commits and count how many "votes" each of them has.
@@ -196,11 +209,14 @@ impl Canonical {
            }
        }
        // Keep commits which pass the threshold.
-
        candidates.retain(|_, votes| *votes >= threshold);
+
        candidates.retain(|_, votes| *votes >= self.threshold);

-
        let (mut longest, _) = candidates
-
            .pop_first()
-
            .ok_or(QuorumError::NoCandidates(NoCandidates { threshold }))?;
+
        let (mut longest, _) =
+
            candidates
+
                .pop_first()
+
                .ok_or(QuorumError::NoCandidates(NoCandidates {
+
                    threshold: self.threshold,
+
                }))?;

        // Now that all scores are calculated, figure out what is the longest branch
        // that passes the threshold. In case of divergence, return an error.
@@ -235,7 +251,7 @@ impl Canonical {
                //            |
                //
                return Err(QuorumError::Diverging(Diverging {
-
                    threshold,
+
                    threshold: self.threshold,
                    base: base.into(),
                    longest,
                    head: *head,
@@ -271,7 +287,7 @@ mod tests {
                (did, (*head).into())
            })
            .collect();
-
        Canonical { tips }.quorum(threshold, repo)
+
        Canonical { tips, threshold }.quorum(repo)
    }

    #[test]
modified crates/radicle/src/storage/git.rs
@@ -752,8 +752,9 @@ impl ReadRepository for Repository {
        let project = doc.project()?;
        let branch_ref = git::refs::branch(project.default_branch());
        let raw = self.raw();
-
        let oid = Canonical::default_branch(self, &project, doc.delegates().into())?
-
            .quorum(doc.threshold(), raw)?;
+
        let oid =
+
            Canonical::default_branch(self, &project, doc.delegates().into(), doc.threshold())?
+
                .quorum(raw)?;
        Ok((branch_ref, oid))
    }