From b145b95ee4205a1d18fbe6fb912f11c3dd864526 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Mon, 30 Mar 2026 17:57:28 +0200 Subject: [PATCH] radicle/cob/identity: Use verdicts to count votes Evaluation of this COB is implemented under the assumption that delegates accept at most one revision. This can lead to issues, if some delegates create new revisions eagerly, without waiting for the others to vote on earlier revisions. As the eager delegates start accepting newer and newer revisions, this shadows their acceptance of earlier revisions, which leads to failure to recognize that these earlier revisions were actually accepted by a majority. To avoid such situations, remove `heads`, and always count the number of "accept" verdicts explicitly. --- crates/radicle/src/cob/identity.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/crates/radicle/src/cob/identity.rs b/crates/radicle/src/cob/identity.rs index bf037e90..368e85d4 100644 --- a/crates/radicle/src/cob/identity.rs +++ b/crates/radicle/src/cob/identity.rs @@ -177,9 +177,6 @@ pub struct Identity { pub current: RevisionId, /// The initial revision of the document. pub root: RevisionId, - /// The latest revision that each delegate has accepted. - /// Delegates can only accept one revision at a time. - pub heads: BTreeMap, /// Revisions. revisions: BTreeMap>, @@ -209,12 +206,6 @@ impl Identity { id: revision.blob.into(), root, current: root, - heads: revision - .delegates() - .iter() - .copied() - .map(|did| (did, root)) - .collect(), revisions: BTreeMap::from_iter([(root, Some(revision))]), timeline: vec![root], } @@ -493,7 +484,6 @@ impl Identity { } assert_eq!(revision.parent, Some(current.id)); - self.heads.insert(author.into(), id); revision.accept(author, signature, ¤t)?; self.adopt(id); @@ -606,7 +596,6 @@ impl Identity { ); let id = revision.id; - self.heads.insert(author.into(), id); self.revisions.insert(id, Some(revision)); if state == State::Active { @@ -623,10 +612,9 @@ impl Identity { return; } let votes = self - .heads - .values() - .filter(|revision| **revision == id) - .count(); + .revision(&id) + .map(|revision| revision.accepted().count()) + .unwrap_or_default(); if self.is_majority(votes) { self.current = id; self.current_mut().state = State::Accepted;