radicle: store threshold in Canonical

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.
This commit is contained in:
Fintan Halpenny 2024-07-12 10:52:44 +01:00
parent b614167bd9
commit b2bcd561cf
3 changed files with 21 additions and 11 deletions

View File

@ -280,6 +280,7 @@ pub fn run(
stored, stored,
&project, &project,
identity.delegates().as_ref(), identity.delegates().as_ref(),
identity.threshold(),
)?; )?;
let converges = canonical::converges( let converges = canonical::converges(
canonical canonical
@ -292,7 +293,7 @@ pub fn run(
canonical.modify_vote(me, head.into()); canonical.modify_vote(me, head.into());
} }
match canonical.quorum(identity.threshold(), &working) { match canonical.quorum(&working) {
Ok(canonical_oid) => { Ok(canonical_oid) => {
// Canonical head is an ancestor of head. // Canonical head is an ancestor of head.
let is_ff = head == *canonical_oid let is_ff = head == *canonical_oid

View File

@ -21,8 +21,10 @@ use super::{lit, Oid, Qualified};
/// ///
/// `Canonical` can then be used for performing calculations about the /// `Canonical` can then be used for performing calculations about the
/// canonicity of the reference, most importantly the [`Canonical::quorum`]. /// canonicity of the reference, most importantly the [`Canonical::quorum`].
#[derive(Debug)]
pub struct Canonical { pub struct Canonical {
tips: BTreeMap<Did, Oid>, tips: BTreeMap<Did, Oid>,
threshold: usize,
} }
/// Error that can occur when calculation the [`Canonical::quorum`]. /// Error that can occur when calculation the [`Canonical::quorum`].
@ -90,6 +92,7 @@ impl Canonical {
repo: &S, repo: &S,
project: &Project, project: &Project,
delegates: &NonEmpty<Did>, delegates: &NonEmpty<Did>,
threshold: usize,
) -> Result<Self, raw::Error> ) -> Result<Self, raw::Error>
where where
S: ReadRepository, S: ReadRepository,
@ -98,6 +101,7 @@ impl Canonical {
repo, repo,
delegates, delegates,
&lit::refs_heads(project.default_branch()).into(), &lit::refs_heads(project.default_branch()).into(),
threshold,
) )
} }
@ -107,6 +111,7 @@ impl Canonical {
repo: &S, repo: &S,
delegates: &NonEmpty<Did>, delegates: &NonEmpty<Did>,
name: &Qualified, name: &Qualified,
threshold: usize,
) -> Result<Self, raw::Error> ) -> Result<Self, raw::Error>
where where
S: ReadRepository, S: ReadRepository,
@ -127,7 +132,7 @@ impl Canonical {
Err(e) => return Err(e), Err(e) => return Err(e),
} }
} }
Ok(Canonical { tips }) Ok(Canonical { tips, threshold })
} }
/// Return the set of [`Did`]s and their [`Oid`] tip. /// Return the set of [`Did`]s and their [`Oid`] tip.
@ -171,7 +176,7 @@ impl Canonical {
/// ///
/// Also returns an error if `heads` is empty or `threshold` cannot be /// Also returns an error if `heads` is empty or `threshold` cannot be
/// satisified with the number of heads given. /// 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(); let mut candidates = BTreeMap::<_, usize>::new();
// Build a list of candidate commits and count how many "votes" each of them has. // Build a list of candidate commits and count how many "votes" each of them has.
@ -196,11 +201,14 @@ impl Canonical {
} }
} }
// Keep commits which pass the threshold. // Keep commits which pass the threshold.
candidates.retain(|_, votes| *votes >= threshold); candidates.retain(|_, votes| *votes >= self.threshold);
let (mut longest, _) = candidates let (mut longest, _) =
.pop_first() candidates
.ok_or(QuorumError::NoCandidates(NoCandidates { threshold }))?; .pop_first()
.ok_or(QuorumError::NoCandidates(NoCandidates {
threshold: self.threshold,
}))?;
// Now that all scores are calculated, figure out what is the longest branch // Now that all scores are calculated, figure out what is the longest branch
// that passes the threshold. In case of divergence, return an error. // that passes the threshold. In case of divergence, return an error.
@ -235,7 +243,7 @@ impl Canonical {
// | // |
// //
return Err(QuorumError::Diverging(Diverging { return Err(QuorumError::Diverging(Diverging {
threshold, threshold: self.threshold,
base: base.into(), base: base.into(),
longest, longest,
head: *head, head: *head,
@ -271,7 +279,7 @@ mod tests {
(did, (*head).into()) (did, (*head).into())
}) })
.collect(); .collect();
Canonical { tips }.quorum(threshold, repo) Canonical { tips, threshold }.quorum(repo)
} }
#[test] #[test]

View File

@ -752,8 +752,9 @@ impl ReadRepository for Repository {
let project = doc.project()?; let project = doc.project()?;
let branch_ref = git::refs::branch(project.default_branch()); let branch_ref = git::refs::branch(project.default_branch());
let raw = self.raw(); let raw = self.raw();
let oid = Canonical::default_branch(self, &project, doc.delegates().into())? let oid =
.quorum(doc.threshold(), raw)?; Canonical::default_branch(self, &project, doc.delegates().into(), doc.threshold())?
.quorum(raw)?;
Ok((branch_ref, oid)) Ok((branch_ref, oid))
} }