p2panda_auth/group/crdt/
mod.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3pub(crate) mod state;
4
5use std::collections::{HashMap, HashSet};
6use std::fmt::{Debug, Display};
7use std::marker::PhantomData;
8
9use petgraph::algo::toposort;
10use petgraph::prelude::DiGraphMap;
11use petgraph::visit::{DfsPostOrder, IntoNodeIdentifiers, NodeIndexable, Reversed};
12use thiserror::Error;
13
14use crate::access::Access;
15use crate::group::{
16    GroupAction, GroupControlMessage, GroupMember, GroupMembersState, GroupMembershipError,
17};
18use crate::traits::{
19    GroupMembership, GroupStore, IdentityHandle, Operation, OperationId, Orderer, Resolver,
20};
21
22/// Error types for GroupCrdt.
23#[derive(Debug, Error)]
24pub enum GroupCrdtError<ID, OP, C, RS, ORD, GS>
25where
26    ID: IdentityHandle,
27    OP: OperationId + Ord,
28    RS: Resolver<ID, OP, C, ORD, GS>,
29    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
30    GS: GroupStore<ID, OP, C, RS, ORD>,
31{
32    #[error("duplicate operation {0} processed in group {1}")]
33    DuplicateOperation(OP, ID),
34
35    #[error("state change error processing operation {0}: {1:?}")]
36    StateChangeError(OP, GroupMembershipError<GroupMember<ID>>),
37
38    #[error("expected sub-group {0} to exist in the store")]
39    MissingSubGroup(ID),
40
41    #[error("ordering error: {0}")]
42    OrderingError(ORD::Error),
43
44    #[error("group store error: {0}")]
45    GroupStoreError(GS::Error),
46
47    #[error("states {0:?} not found in group {1}")]
48    StatesNotFound(Vec<OP>, ID),
49
50    #[error("expected dependencies {0:?} not found in group {1}")]
51    DependenciesNotFound(Vec<OP>, ID),
52
53    #[error("operation for group {0} processed in group {1}")]
54    IncorrectGroupId(ID, ID),
55
56    #[error("operation id {0} exists in the graph but the corresponding operation was not found")]
57    MissingOperation(OP),
58
59    // TODO(glyph): I don't think this variant should live here. Maybe another error type?
60    #[error("state not found for group member {0} in group {1}")]
61    MemberNotFound(ID, ID),
62}
63
64/// State object for `GroupCrdt` containing the operation graph and all incremental group states.
65///
66/// Requires access to a global orderer and group store.
67#[derive(Debug)]
68#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
69pub struct GroupCrdtState<ID, OP, C, RS, ORD, GS>
70where
71    ID: IdentityHandle,
72    OP: OperationId,
73    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
74    GS: GroupStore<ID, OP, C, RS, ORD>,
75{
76    /// ID of the local actor.
77    pub my_id: ID,
78
79    /// ID of the group.
80    pub group_id: ID,
81
82    /// Group state at every position in the operation graph.
83    pub states: HashMap<OP, GroupMembersState<GroupMember<ID>, C>>,
84
85    /// All operations processed by this group.
86    pub operations: HashMap<OP, ORD::Operation>,
87
88    /// All operations who's actions should be ignored.
89    pub ignore: HashSet<OP>,
90
91    /// Operation graph for this group.
92    pub graph: DiGraphMap<OP, ()>,
93
94    /// State for the orderer.
95    pub orderer_y: ORD::State,
96
97    /// All groups known to this instance.
98    pub group_store: GS,
99
100    _phantom: PhantomData<RS>,
101}
102
103impl<ID, OP, C, RS, ORD, GS> GroupCrdtState<ID, OP, C, RS, ORD, GS>
104where
105    ID: IdentityHandle,
106    OP: OperationId + Ord,
107    C: Clone + Debug + PartialEq + PartialOrd,
108    RS: Resolver<ID, OP, C, ORD, GS> + Debug,
109    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
110    GS: GroupStore<ID, OP, C, RS, ORD> + Debug,
111{
112    /// Instantiate a new group state.
113    pub fn new(my_id: ID, group_id: ID, group_store: GS, orderer_y: ORD::State) -> Self {
114        Self {
115            my_id,
116            group_id,
117            states: Default::default(),
118            operations: Default::default(),
119            ignore: Default::default(),
120            graph: Default::default(),
121            group_store,
122            orderer_y,
123            _phantom: PhantomData,
124        }
125    }
126
127    /// Id of this group.
128    pub fn id(&self) -> ID {
129        self.group_id
130    }
131
132    /// Current tips for the group operation graph.
133    pub fn heads(&self) -> HashSet<OP> {
134        self.graph
135            // TODO: clone required here when converting the GraphMap into a Graph. We do this
136            // because the GraphMap api does not include the "externals" method, where as the
137            // Graph api does. We use GraphMap as we can then access nodes by the id we assign
138            // them rather than the internally assigned id generated when using Graph. We can use
139            // Graph and track the indexes ourselves in order to avoid this conversion, or maybe
140            // there is a way to get "externals" on GraphMap (which I didn't find yet). More
141            // investigation required.
142            .clone()
143            .into_graph::<usize>()
144            .externals(petgraph::Direction::Outgoing)
145            .map(|idx| self.graph.from_index(idx.index()))
146            .collect::<HashSet<_>>()
147    }
148
149    /// Current tips of the group operation graph including all sub-groups.
150    #[allow(clippy::type_complexity)]
151    pub fn transitive_heads(&self) -> Result<HashSet<OP>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
152        let mut transitive_heads = self.heads();
153        for (member, ..) in self.members() {
154            if let GroupMember::Group(id) = member {
155                let sub_group = self.get_sub_group(id)?;
156                transitive_heads.extend(sub_group.transitive_heads()?);
157            }
158        }
159
160        Ok(transitive_heads)
161    }
162
163    /// Current state of this group.
164    ///
165    /// This method gets the state at all graph tips and then merges them together into one new
166    /// state which represents the current state of the group.
167    pub fn current_state(&self) -> GroupMembersState<GroupMember<ID>, C> {
168        let mut current_state = GroupMembersState::default();
169        for state in self.heads() {
170            // Unwrap as all "head" states should exist.
171            let state = self.states.get(&state).unwrap();
172            current_state = state::merge(state.clone(), current_state);
173        }
174        current_state
175    }
176
177    fn state_at_inner(&self, dependencies: &HashSet<OP>) -> GroupMembersState<GroupMember<ID>, C> {
178        let mut y = GroupMembersState::default();
179        for id in dependencies.iter() {
180            let Some(previous_y) = self.states.get(id) else {
181                // We might be in a sub-group here processing dependencies which don't exist in
182                // this graph, in that case we just ignore missing states.
183                continue;
184            };
185            // Merge all dependency states from this group together.
186            y = state::merge(previous_y.clone(), y);
187        }
188
189        y
190    }
191
192    /// Get the state of a group at a certain point in it's history.
193    pub fn state_at(&self, dependencies: &HashSet<OP>) -> GroupMembersState<GroupMember<ID>, C> {
194        self.state_at_inner(dependencies)
195    }
196
197    fn members_at_inner(&self, dependencies: &HashSet<OP>) -> Vec<(GroupMember<ID>, Access<C>)> {
198        let y = self.state_at_inner(dependencies);
199        y.members
200            .into_iter()
201            .filter_map(|(id, state)| {
202                if state.is_member() {
203                    Some((id, state.access))
204                } else {
205                    None
206                }
207            })
208            .collect::<Vec<_>>()
209    }
210
211    /// Get the group members at a certain point in groups history.
212    pub fn members_at(&self, dependencies: &HashSet<OP>) -> Vec<(GroupMember<ID>, Access<C>)> {
213        self.members_at_inner(dependencies)
214    }
215
216    #[allow(clippy::type_complexity)]
217    fn transitive_members_at_inner(
218        &self,
219        dependencies: &HashSet<OP>,
220    ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
221        let mut members: HashMap<ID, Access<C>> = HashMap::new();
222
223        // Get members of a group at a certain point in the groups history.
224        for (member, root_access) in self.members_at_inner(dependencies) {
225            match member {
226                GroupMember::Individual(id) => {
227                    // If this is an individual member, then add them straight to the members map.
228                    members.insert(id, root_access.clone());
229                }
230                GroupMember::Group(id) => {
231                    // If this is a sub-group member, then get the sub-group state from the store
232                    // and recurse into the group passing the dependencies set which identify the
233                    // particular states we're interested in.
234                    let sub_group = self.get_sub_group(id)?;
235
236                    // The access level for all transitive members must not be greater than the
237                    // access level assigned to the current sub-group.
238                    let transitive_members = sub_group.transitive_members_at_inner(dependencies)?;
239
240                    // For each transitive member, add them to the members map if they were not
241                    // already a member, assigning them the correct access level. If they were
242                    // already a member, then modify their existing access level _if_ it elevates
243                    // their access to a higher level, but not higher than the current sub.
244                    for (transitive_member, transitive_access) in transitive_members {
245                        let root_access_copy = root_access.clone();
246                        members
247                            .entry(transitive_member)
248                            .and_modify(|access| {
249                                // If the transitive access level this member holds (the access
250                                // level the member has in it's sub-group) is greater than it's
251                                // current access level, but not greater than the root access
252                                // level (the access level initially assigned from the parent
253                                // group) then update the access level.
254                                if transitive_access > *access
255                                    && transitive_access <= root_access_copy
256                                {
257                                    *access = transitive_access.clone()
258                                }
259                            })
260                            .or_insert_with(|| {
261                                if transitive_access <= root_access_copy {
262                                    transitive_access
263                                } else {
264                                    root_access_copy
265                                }
266                            });
267                    }
268                }
269            }
270        }
271
272        Ok(members.into_iter().collect())
273    }
274
275    /// Get all transitive members of the group at a certain point in it's history.
276    ///
277    /// This method recurses into all sub-groups collecting all "tip" members, which are the
278    /// stateless "individual" members of a group, likely identified by a public key.
279    #[allow(clippy::type_complexity)]
280    pub fn transitive_members_at(
281        &self,
282        dependencies: &HashSet<OP>,
283    ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
284        let members = self.transitive_members_at_inner(dependencies)?;
285        Ok(members)
286    }
287
288    /// Get all current members of the group.
289    pub fn members(&self) -> Vec<(GroupMember<ID>, Access<C>)> {
290        self.current_state()
291            .members
292            .into_iter()
293            .filter_map(|(id, state)| {
294                if state.is_member() {
295                    Some((id, state.access))
296                } else {
297                    None
298                }
299            })
300            .collect::<Vec<_>>()
301    }
302
303    /// Get all current transitive members of the group.
304    ///
305    /// This method recurses into all sub-groups collecting all "tip" members, which are the
306    /// stateless "individual" members of a group, likely identified by a public key.
307    #[allow(clippy::type_complexity)]
308    pub fn transitive_members(
309        &self,
310    ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
311        let heads = self.transitive_heads()?;
312        let members = self.transitive_members_at(&heads)?;
313
314        Ok(members)
315    }
316
317    /// Get a sub group from the group store.
318    #[allow(clippy::type_complexity)]
319    pub(crate) fn get_sub_group(
320        &self,
321        id: ID,
322    ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
323    {
324        let y = self
325            .group_store
326            .get(&id)
327            .map_err(|error| GroupCrdtError::GroupStoreError(error))?;
328
329        // We expect that groups are created and correctly present in the store before we process
330        // any messages requiring us to query them, so this error can only occur if there is an
331        // error in any higher orchestration system.
332        let Some(y) = y else {
333            return Err(GroupCrdtError::MissingSubGroup(id));
334        };
335
336        Ok(y)
337    }
338
339    /// Get the maximum given access level for an actor at a certain point in the auth graph.
340    ///
341    /// An actor can be a direct individual member of a group, or a transitive member via a
342    /// sub-group. This is a helper method which finds the hightest access level a member has and
343    /// returns the group member which gives this actor the found access level.
344    ///
345    /// The passed dependencies array tells us which position in the graph to look at.
346    #[allow(clippy::type_complexity)]
347    pub fn max_access_identity(
348        &self,
349        actor: ID,
350        dependencies: &HashSet<OP>,
351    ) -> Result<Option<(GroupMember<ID>, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
352        let mut access_levels = Vec::new();
353
354        // Get members of a group at a certain point in the groups history.
355        for (member, root_access) in self.members_at(dependencies) {
356            match member {
357                // If this is an individual matching the actor then push it to the access levels
358                // vector.
359                GroupMember::Individual(id) => {
360                    if id == actor {
361                        access_levels.push((member, root_access));
362                    }
363                }
364                // If this is a group, then look into all transitive members to find any matches
365                // to the passed actor id.
366                GroupMember::Group(id) => {
367                    let sub_group = self.get_sub_group(id)?;
368                    if let Some((_, transitive_access)) = sub_group
369                        // @TODO: we would prefer to call transitive_members() here so as to
370                        //        account for the most recent sub-group state we know about. To do
371                        //        this we first need to adjust how sub-group states are attached
372                        //        to the root group graph.
373                        .transitive_members_at(dependencies)
374                        .unwrap()
375                        .iter()
376                        .find(|(member, _)| *member == actor)
377                    {
378                        // The actual access can't be greater than the access which was originally
379                        // given to the sub-group.
380                        let actual_access = if transitive_access < &root_access {
381                            transitive_access.clone()
382                        } else {
383                            root_access.clone()
384                        };
385                        access_levels.push((member, actual_access));
386                    }
387                }
388            }
389        }
390
391        let mut max_access: Option<(GroupMember<ID>, Access<C>)> = None;
392        for (id, access) in access_levels {
393            match max_access.clone() {
394                Some((_, prev_access)) => {
395                    if prev_access < access {
396                        max_access = Some((id, access))
397                    }
398                }
399                None => max_access = Some((id, access)),
400            }
401        }
402        Ok(max_access)
403    }
404}
405
406impl<ID, OP, C, RS, ORD, GS> GroupMembership<ID, OP, C> for GroupCrdtState<ID, OP, C, RS, ORD, GS>
407where
408    ID: IdentityHandle + Display,
409    OP: OperationId + Ord + Display,
410    C: Clone + Debug + PartialEq + PartialOrd,
411    RS: Resolver<ID, OP, C, ORD, GS> + Debug,
412    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
413    GS: GroupStore<ID, OP, C, RS, ORD> + Debug,
414{
415    type State = GroupCrdtState<ID, OP, C, RS, ORD, GS>;
416
417    type Error = GroupCrdtError<ID, OP, C, RS, ORD, GS>;
418
419    /// Query the current access level of the given member.
420    ///
421    /// The member is expected to be a "stateless" individual, not a "stateful" group.
422    fn access(
423        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
424        member: &ID,
425    ) -> Result<Access<C>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
426        let member_state = y
427            .transitive_members()?
428            .into_iter()
429            .find(|(member_id, _state)| member_id == member);
430
431        if let Some(state) = member_state {
432            let access = state.1.to_owned();
433
434            Ok(access)
435        } else {
436            Err(GroupCrdtError::MemberNotFound(y.group_id, *member))
437        }
438    }
439
440    /// Query group membership.
441    fn member_ids(
442        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
443    ) -> Result<HashSet<ID>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
444        let member_ids = y
445            .transitive_members()?
446            .into_iter()
447            .map(|(member_id, _state)| member_id)
448            .collect();
449
450        Ok(member_ids)
451    }
452
453    /// Return `true` if the given ID is an active member of the group.
454    fn is_member(
455        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
456        member: &ID,
457    ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
458        let member_state = y
459            .transitive_members()?
460            .into_iter()
461            .find(|(member_id, _state)| member_id == member);
462
463        let is_member = member_state.is_some();
464
465        Ok(is_member)
466    }
467
468    /// Return `true` if the given member is currently assigned the `Pull` access level.
469    fn is_puller(
470        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
471        member: &ID,
472    ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
473        Ok(GroupCrdtState::access(y, member)?.is_pull())
474    }
475
476    /// Return `true` if the given member is currently assigned the `Read` access level.
477    fn is_reader(
478        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
479        member: &ID,
480    ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
481        Ok(GroupCrdtState::access(y, member)?.is_read())
482    }
483
484    /// Return `true` if the given member is currently assigned the `Write` access level.
485    fn is_writer(
486        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
487        member: &ID,
488    ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
489        Ok(GroupCrdtState::access(y, member)?.is_write())
490    }
491
492    /// Return `true` if the given member is currently assigned the `Manage` access level.
493    fn is_manager(
494        y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
495        member: &ID,
496    ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
497        Ok(GroupCrdtState::access(y, member)?.is_manage())
498    }
499}
500
501/// Core group CRDT for maintaining group membership state in a decentralized system.
502///
503/// Group members can be assigned different access levels, where only a sub-set of members can
504/// mutate the state of the group itself. Group members can be (immutable) individuals or
505/// (mutable) sub-groups.
506///
507/// The core data type is a Directed Acyclic Graph of operations containing group management
508/// actions. Operations refer to the "previous" state (set of graph tips) which the action they
509/// contain should be applied to; these references make up the edges in the graph. Additionally,
510/// operations have a set of "dependencies" which could be part of any sub-group.
511///
512/// A requirement of the protocol is that all messages are processed in partial-order. When using
513/// a dependency graph structure (as is the case in this implementation) it is possible to achieve
514/// partial-ordering by only processing a message once all it's dependencies have themselves been
515/// processed.
516///
517/// Group state is maintained using the state object `GroupMembersState`. Every time an action is
518/// processed, a new state is generated and added to the map of all states. When a new operation
519/// is received, it's "previous" state is calculated and then the message applied, resulting in a
520/// new state.
521///
522/// Group membership rules are checked when an action is applied to the previous state, read more
523/// in the `crdt::state` module.
524///
525/// The struct has several generic parameters which allow users to specify their own core types
526/// and to customise behavior when handling concurrent changes when resolving a graph to it's
527/// final state.
528///
529/// - ID : identifier for both an individual actor and group.
530/// - OP : identifier for an operation.
531/// - C  : conditions which restrict an access level.
532/// - RS : generic resolver which contains logic for deciding when group state rebuilds are
533///   required, and how concurrent actions are handled. See the `resolver` module for different
534///   implementations.
535/// - ORD: orderer which exposes an API for creating and processing operations with meta-data
536///   which allow them to be processed in partial order.
537/// - GS : global store containing states for all known groups.
538#[derive(Clone, Debug, Default)]
539pub struct GroupCrdt<ID, OP, C, RS, ORD, GS> {
540    _phantom: PhantomData<(ID, OP, C, RS, ORD, GS)>,
541}
542
543impl<ID, OP, C, RS, ORD, GS> GroupCrdt<ID, OP, C, RS, ORD, GS>
544where
545    ID: IdentityHandle + Display,
546    OP: OperationId + Ord + Display,
547    C: Clone + Debug + PartialEq + PartialOrd,
548    RS: Resolver<ID, OP, C, ORD, GS> + Debug,
549    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
550    ORD::Operation: Clone,
551    GS: GroupStore<ID, OP, C, RS, ORD> + Clone + Debug,
552{
553    /// Prepare a next operation to be processed locally and sent to remote peers. An ORD
554    /// implementation needs to ensure "previous" and "dependencies" are populated correctly so
555    /// that a partial-order of all operations in the system can be established.
556    ///
557    /// The method `GroupCrdtState::heads` and `GroupCrdtState::transitive_heads` can be used to retrieve the
558    /// operation ids of these operation dependencies.
559    #[allow(clippy::type_complexity)]
560    pub fn prepare(
561        mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
562        action: &GroupControlMessage<ID, C>,
563    ) -> Result<
564        (GroupCrdtState<ID, OP, C, RS, ORD, GS>, ORD::Operation),
565        GroupCrdtError<ID, OP, C, RS, ORD, GS>,
566    > {
567        // Get the next operation from our global orderer. The operation wraps the action we want
568        // to perform, adding ordering and author meta-data.
569        let ordering_y = y.orderer_y;
570        let (ordering_y, operation) = match ORD::next_message(ordering_y, action) {
571            Ok(operation) => operation,
572            Err(_) => panic!(),
573        };
574
575        y.orderer_y = ordering_y;
576        Ok((y, operation))
577    }
578
579    /// Process an operation created locally or received from a remote peer.
580    #[allow(clippy::type_complexity)]
581    pub fn process(
582        mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
583        operation: &ORD::Operation,
584    ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
585    {
586        let operation_id = operation.id();
587        let actor = operation.author();
588        let control_message = operation.payload();
589        let previous_operations = operation.previous();
590        let dependencies = HashSet::from_iter(operation.dependencies().clone());
591        let group_id = control_message.group_id();
592
593        if y.group_id != group_id {
594            // The operation is not intended for this group.
595            return Err(GroupCrdtError::IncorrectGroupId(group_id, y.group_id));
596        }
597
598        if y.operations.contains_key(&operation_id) {
599            // The operation has already been processed.
600            return Err(GroupCrdtError::DuplicateOperation(operation_id, group_id));
601        }
602
603        // The resolver implementation contains the logic which determines when rebuilds are
604        // required.
605        let rebuild_required = RS::rebuild_required(&y, operation)?;
606
607        // Add the new operation to the group state graph and operations vec. We validate it in
608        // the following steps.
609        y.graph.add_node(operation_id);
610        for previous in &previous_operations {
611            y.graph.add_edge(*previous, operation_id, ());
612        }
613
614        y.operations.insert(operation_id, operation.clone());
615
616        let y_i = if rebuild_required {
617            // Validate a concurrent operation against it's previous states.
618            //
619            // To do this we need to prune the graph to only include predecessor operations,
620            // re-calculate the filter, and re-build all states.
621            y = GroupCrdt::validate_concurrent_action(y, operation)?;
622
623            // Process the group state with the provided resolver. This will populate the set of
624            // messages which should be ignored when applying group management actions and also
625            // rebuilds the group state (including the new operation).
626            RS::process(y)?
627        } else {
628            // Compute the member's state by applying the new operation to the current group
629            // state.
630            //
631            // This method validates that the actor has permission to perform the action.
632            match Self::apply_action(
633                y,
634                operation_id,
635                actor,
636                &dependencies,
637                &control_message.action,
638            )? {
639                StateChangeResult::Ok { state } => state,
640                StateChangeResult::Noop { error, .. } => {
641                    return Err(GroupCrdtError::StateChangeError(operation_id, error));
642                }
643                StateChangeResult::Filtered { .. } => {
644                    // Operations can't be filtered out before they were processed.
645                    unreachable!()
646                }
647            }
648        };
649
650        // Update the group in the store.
651        y_i.group_store
652            .insert(&group_id, &y_i)
653            .map_err(|error| GroupCrdtError::GroupStoreError(error))?;
654
655        Ok(y_i)
656    }
657
658    /// Apply an action to a single group state.
659    #[allow(clippy::type_complexity)]
660    pub(crate) fn apply_action(
661        mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
662        id: OP,
663        actor: ID,
664        dependencies: &HashSet<OP>,
665        action: &GroupAction<ID, C>,
666    ) -> Result<StateChangeResult<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
667    {
668        // Compute the member's state by applying the new operation to it's claimed "dependencies"
669        // state.
670        let members_y = if dependencies.is_empty() {
671            GroupMembersState::default()
672        } else {
673            y.state_at(dependencies)
674        };
675
676        // Get the maximum access level for this actor.
677        let max_access = y.max_access_identity(actor, dependencies)?;
678        let member_id = match max_access {
679            Some((id, _)) => id,
680            None => GroupMember::Individual(actor),
681        };
682
683        // Only add the resulting member's state to the states map if the operation isn't
684        // flagged to be ignored.
685        if !y.ignore.contains(&id) {
686            let result = match action.clone() {
687                GroupAction::Add { member, access, .. } => {
688                    state::add(members_y.clone(), member_id, member, access)
689                }
690                GroupAction::Remove { member, .. } => {
691                    state::remove(members_y.clone(), member_id, member)
692                }
693                GroupAction::Promote { member, access } => {
694                    state::promote(members_y.clone(), member_id, member, access)
695                }
696                GroupAction::Demote { member, access } => {
697                    state::demote(members_y.clone(), member_id, member, access)
698                }
699                GroupAction::Create { initial_members } => Ok(state::create(&initial_members)),
700            };
701
702            match result {
703                Ok(members_y_i) => y.states.insert(id, members_y_i),
704                Err(err) => {
705                    // Errors occur here because the member attempting to perform an action
706                    // doesn't have a suitable access level, or that the action itself is invalid
707                    // (eg. promoting a non-existent member).
708                    //
709                    // 1) We expect some errors to occur when when intentionally filtered out
710                    //    actions cause later operations to become invalid.
711                    //
712                    // 2) Operations which other peers accepted into their graph _before_
713                    //    receiving some concurrent operation which caused them to be invalid.
714                    //
715                    // In both cases it's critical that the action does not cause any state
716                    // change, however we do want to accept them into our graph so as to ensure
717                    // consistency consistency across peers.
718                    y.states.insert(id, members_y);
719                    return Ok(StateChangeResult::Noop {
720                        state: y,
721                        error: err,
722                    });
723                }
724            };
725        } else {
726            y.states.insert(id, members_y);
727            return Ok(StateChangeResult::Filtered { state: y });
728        }
729        Ok(StateChangeResult::Ok { state: y })
730    }
731
732    /// Validate an action by applying it to the group state build to it's previous pointers.
733    ///
734    /// When processing an new operation we need to validate that the contained action is valid
735    /// before including it in the graph. By valid we mean that the author who composed the action
736    /// had authority to perform the claimed action, and that the action fulfils all group change
737    /// requirements. To check this we need to re-build the group state to the operations claimed
738    /// "previous" state. This process involves pruning any operations which are not predecessors
739    /// of the new operation resolving the group state again.
740    ///
741    /// This is a relatively expensive computation and should only be used when a re-build is
742    /// actually required.
743    #[allow(clippy::type_complexity)]
744    pub(crate) fn validate_concurrent_action(
745        mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
746        operation: &ORD::Operation,
747    ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
748    {
749        // Keep hold of original operations and graph.
750        let last_graph = y.graph.clone();
751        let last_ignore = y.ignore.clone();
752        let last_states = y.states.clone();
753
754        // Collect predecessors of the new operation.
755        let mut predecessors = HashSet::new();
756        for previous in operation.previous() {
757            let reversed = Reversed(&y.graph);
758            let mut dfs_rev = DfsPostOrder::new(&reversed, previous);
759            while let Some(id) = dfs_rev.next(&reversed) {
760                predecessors.insert(id);
761            }
762        }
763
764        // Remove all other nodes from the graph.
765        let to_remove: Vec<_> = y
766            .graph
767            .node_identifiers()
768            .filter(|n| !predecessors.contains(n))
769            .collect();
770
771        for node in &to_remove {
772            y.graph.remove_node(*node);
773        }
774
775        y = RS::process(y)?;
776
777        let dependencies = HashSet::from_iter(operation.dependencies().clone());
778
779        let mut y_i = match Self::apply_action(
780            y,
781            operation.id(),
782            operation.author(),
783            &dependencies,
784            &operation.payload().action,
785        )? {
786            StateChangeResult::Ok { state } => state,
787            StateChangeResult::Noop { error, .. } => {
788                return Err(GroupCrdtError::StateChangeError(operation.id(), error));
789            }
790            StateChangeResult::Filtered { .. } => {
791                // Operations can't be filtered out before they were processed.
792                unreachable!()
793            }
794        };
795
796        y_i.graph = last_graph;
797        y_i.ignore = last_ignore;
798        y_i.states = last_states;
799
800        Ok(y_i)
801    }
802
803    /// Rebuild the group state.
804    ///
805    /// This method assumes that a new filter has already calculated and added to the group state.
806    /// No graph traversal occurs, all operations are simply iterated over and applied to the
807    /// group state if they are not explicitly filtered. Errors resulting from "no-op" operations
808    /// (operations which became invalid because they are transitively dependent on filtered
809    /// operations) are expected and therefore not propagated further.
810    #[allow(clippy::type_complexity)]
811    pub(crate) fn rebuild(
812        y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
813    ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
814    {
815        let mut y_i = GroupCrdtState::new(y.my_id, y.group_id, y.group_store, y.orderer_y);
816        y_i.ignore = y.ignore;
817        let operations = y.operations;
818
819        let topo_sort =
820            toposort(&y.graph, None).expect("group operation sets can be ordered topologically");
821
822        // Apply every operation.
823        let mut create_found = false;
824        for operation_id in topo_sort {
825            let operation = operations
826                .get(&operation_id)
827                .expect("all processed operations exist");
828            let actor = operation.author();
829            let operation_id = operation.id();
830            let control_message = operation.payload();
831            let group_id = control_message.group_id();
832            let dependencies = HashSet::from_iter(operation.dependencies().clone());
833
834            // Sanity check: we should only apply operations for this group.
835            assert_eq!(y_i.group_id, group_id);
836
837            // Sanity check: the first operation must be a create and all other operations must not be.
838            if create_found {
839                assert!(!control_message.is_create())
840            } else {
841                assert!(control_message.is_create())
842            }
843
844            create_found = true;
845
846            y_i = match Self::apply_action(
847                y_i,
848                operation_id,
849                actor,
850                &dependencies,
851                &control_message.action,
852            )? {
853                StateChangeResult::Ok { state } => state,
854                StateChangeResult::Noop { state, .. } => {
855                    // We don't error here as during re-build we expect some operations to
856                    // fail if they've been transitively invalidated by a change in
857                    // filter.
858                    state
859                }
860                StateChangeResult::Filtered { state } => state,
861            };
862
863            // Add the new operation to the group state graph and operations vec.
864            y_i.graph.add_node(operation_id);
865            for previous in &operation.previous() {
866                y_i.graph.add_edge(*previous, operation_id, ());
867            }
868        }
869
870        y_i.operations = operations;
871        Ok(y_i)
872    }
873}
874
875/// Return types expected from applying an action to group state.
876pub enum StateChangeResult<ID, OP, C, RS, ORD, GS>
877where
878    ID: IdentityHandle + Display,
879    OP: OperationId + Ord + Display,
880    C: Clone + Debug + PartialEq + PartialOrd,
881    RS: Resolver<ID, OP, C, ORD, GS> + Debug,
882    ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
883    ORD::Operation: Clone,
884    GS: GroupStore<ID, OP, C, RS, ORD> + Clone + Debug,
885{
886    /// Action was applied and no error occurred.
887    Ok {
888        state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
889    },
890
891    /// Action was not applied because it failed internal validation.
892    Noop {
893        state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
894        #[allow(unused)]
895        error: GroupMembershipError<GroupMember<ID>>,
896    },
897
898    /// Action was not applied because it has been filtered out.
899    Filtered {
900        state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
901    },
902}
903
904#[cfg(test)]
905pub(crate) mod tests {
906
907    use rand::SeedableRng;
908    use rand::rngs::StdRng;
909
910    use crate::Access;
911    use crate::group::{
912        GroupAction, GroupControlMessage, GroupCrdt, GroupCrdtError, GroupCrdtState, GroupMember,
913        GroupMembershipError,
914    };
915    use crate::test_utils::{
916        MessageId, Network, TestGroup, TestGroupState, TestGroupStore, TestOperation,
917        TestOrdererState,
918    };
919
920    pub(crate) fn from_create(
921        actor_id: char,
922        group_id: char,
923        op_create: &TestOperation,
924        rng: &mut StdRng,
925    ) -> TestGroupState {
926        let store = TestGroupStore::default();
927        let orderer = TestOrdererState::new(actor_id, store.clone(), StdRng::from_rng(rng));
928        let group = TestGroupState::new(actor_id, group_id, store, orderer);
929        TestGroup::process(group, op_create).unwrap()
930    }
931
932    pub(crate) fn create_group(
933        actor_id: char,
934        group_id: char,
935        members: Vec<(char, Access<()>)>,
936        rng: &mut StdRng,
937    ) -> (TestGroupState, TestOperation) {
938        let store = TestGroupStore::default();
939        let orderer = TestOrdererState::new(actor_id, store.clone(), StdRng::from_rng(rng));
940        let group = TestGroupState::new(actor_id, group_id, store, orderer);
941        let control_message = GroupControlMessage {
942            group_id,
943            action: GroupAction::Create {
944                initial_members: members
945                    .into_iter()
946                    .map(|(id, access)| (GroupMember::Individual(id), access))
947                    .collect(),
948            },
949        };
950        let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
951        let group = TestGroup::process(group, &op).unwrap();
952        (group, op)
953    }
954
955    pub(crate) fn add_member(
956        group: TestGroupState,
957        group_id: char,
958        member: char,
959        access: Access<()>,
960    ) -> (TestGroupState, TestOperation) {
961        let control_message = GroupControlMessage {
962            group_id,
963            action: GroupAction::Add {
964                member: GroupMember::Individual(member),
965                access,
966            },
967        };
968        let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
969        let group = TestGroup::process(group, &op).unwrap();
970        (group, op)
971    }
972
973    pub(crate) fn remove_member(
974        group: TestGroupState,
975        group_id: char,
976        member: char,
977    ) -> (TestGroupState, TestOperation) {
978        let control_message = GroupControlMessage {
979            group_id,
980            action: GroupAction::Remove {
981                member: GroupMember::Individual(member),
982            },
983        };
984        let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
985        let group = TestGroup::process(group, &op).unwrap();
986        (group, op)
987    }
988
989    pub(crate) fn sync(group: TestGroupState, ops: &[TestOperation]) -> TestGroupState {
990        ops.iter()
991            .fold(group, |g, op| TestGroup::process(g, op).unwrap())
992    }
993
994    pub(crate) fn assert_members(
995        group: &TestGroupState,
996        expected: &[(GroupMember<char>, Access<()>)],
997    ) {
998        let mut actual = group.members();
999        let mut expected = expected.to_vec();
1000        actual.sort();
1001        expected.sort();
1002        assert_eq!(actual, expected);
1003    }
1004
1005    #[test]
1006    fn basic_group() {
1007        let group_id = '1';
1008        let alice = 'A';
1009        let store = TestGroupStore::default();
1010        let rng = StdRng::from_os_rng();
1011        let orderer_y = TestOrdererState::new(alice, store.clone(), rng);
1012        let group_y = TestGroupState::new(alice, group_id, store, orderer_y);
1013
1014        // Create group with alice as initial admin member.
1015        let control_message_001 = GroupControlMessage {
1016            group_id,
1017            action: GroupAction::Create {
1018                initial_members: vec![(GroupMember::Individual(alice), Access::manage())],
1019            },
1020        };
1021        let (group_y, operation_001) = TestGroup::prepare(group_y, &control_message_001).unwrap();
1022        let group_y = TestGroup::process(group_y, &operation_001).unwrap();
1023
1024        let mut members = group_y.members();
1025        members.sort();
1026        assert_eq!(
1027            members,
1028            vec![(GroupMember::Individual(alice), Access::manage())]
1029        );
1030
1031        // Add bob with read access.
1032        let bob = 'B';
1033        let control_message_002 = GroupControlMessage {
1034            group_id,
1035            action: GroupAction::Add {
1036                member: GroupMember::Individual(bob),
1037                access: Access::read(),
1038            },
1039        };
1040        let (group_y, operation_002) = TestGroup::prepare(group_y, &control_message_002).unwrap();
1041        let group_y = TestGroup::process(group_y, &operation_002).unwrap();
1042
1043        let mut members = group_y.members();
1044        members.sort();
1045        assert_eq!(
1046            members,
1047            vec![
1048                (GroupMember::Individual(alice), Access::manage()),
1049                (GroupMember::Individual(bob), Access::read())
1050            ]
1051        );
1052
1053        // Add claire with write access.
1054        let claire = 'C';
1055        let control_message_003 = GroupControlMessage {
1056            group_id,
1057            action: GroupAction::Add {
1058                member: GroupMember::Individual(claire),
1059                access: Access::write(),
1060            },
1061        };
1062        let (group_y, operation_003) = TestGroup::prepare(group_y, &control_message_003).unwrap();
1063        let group_y = TestGroup::process(group_y, &operation_003).unwrap();
1064
1065        let mut members = group_y.members();
1066        members.sort();
1067        assert_eq!(
1068            members,
1069            vec![
1070                (GroupMember::Individual(alice), Access::manage()),
1071                (GroupMember::Individual(bob), Access::read()),
1072                (GroupMember::Individual(claire), Access::write())
1073            ]
1074        );
1075
1076        // Promote claire to admin.
1077        let control_message_004 = GroupControlMessage {
1078            group_id,
1079            action: GroupAction::Promote {
1080                member: GroupMember::Individual(claire),
1081                access: Access::manage(),
1082            },
1083        };
1084        let (group_y, operation_004) = TestGroup::prepare(group_y, &control_message_004).unwrap();
1085        let group_y = TestGroup::process(group_y, &operation_004).unwrap();
1086
1087        let mut members = group_y.members();
1088        members.sort();
1089        assert_eq!(
1090            members,
1091            vec![
1092                (GroupMember::Individual(alice), Access::manage()),
1093                (GroupMember::Individual(bob), Access::read()),
1094                (GroupMember::Individual(claire), Access::manage())
1095            ]
1096        );
1097
1098        // Demote bob to poll access.
1099        let control_message_005 = GroupControlMessage {
1100            group_id,
1101            action: GroupAction::Demote {
1102                member: GroupMember::Individual(bob),
1103                access: Access::pull(),
1104            },
1105        };
1106        let (group_y, operation_005) = TestGroup::prepare(group_y, &control_message_005).unwrap();
1107        let group_y = TestGroup::process(group_y, &operation_005).unwrap();
1108
1109        let mut members = group_y.members();
1110        members.sort();
1111        assert_eq!(
1112            members,
1113            vec![
1114                (GroupMember::Individual(alice), Access::manage()),
1115                (GroupMember::Individual(bob), Access::pull()),
1116                (GroupMember::Individual(claire), Access::manage())
1117            ]
1118        );
1119
1120        // Remove bob.
1121        let control_message_006 = GroupControlMessage {
1122            group_id,
1123            action: GroupAction::Remove {
1124                member: GroupMember::Individual(bob),
1125            },
1126        };
1127        let (group_y, operation_006) = TestGroup::prepare(group_y, &control_message_006).unwrap();
1128        let group_y = TestGroup::process(group_y, &operation_006).unwrap();
1129
1130        let mut members = group_y.members();
1131        members.sort();
1132        assert_eq!(
1133            members,
1134            vec![
1135                (GroupMember::Individual(alice), Access::manage()),
1136                (GroupMember::Individual(claire), Access::manage())
1137            ]
1138        );
1139    }
1140
1141    #[test]
1142    fn nested_groups() {
1143        let alice = 'A';
1144        let alice_mobile = 'M';
1145        let alice_laptop = 'L';
1146
1147        let alice_devices_group = 'D';
1148        let alice_team_group = 'T';
1149
1150        // The group store is shared state across all group instances.
1151        let store = TestGroupStore::default();
1152        let rng = StdRng::from_os_rng();
1153        let alice_orderer_y = TestOrdererState::new(alice, store.clone(), rng);
1154
1155        // One devices group instance.
1156        let devices_group_y = GroupCrdtState::new(
1157            alice,
1158            alice_devices_group,
1159            store.clone(),
1160            alice_orderer_y.clone(),
1161        );
1162
1163        // One team group instance.
1164        let team_group_y =
1165            GroupCrdtState::new(alice, alice_team_group, store.clone(), alice_orderer_y);
1166
1167        // Control message creating the devices group, with alice, alice_laptop and alice mobile as members.
1168        let control_message_001 = GroupControlMessage {
1169            group_id: devices_group_y.id(),
1170            action: GroupAction::Create {
1171                initial_members: vec![
1172                    (GroupMember::Individual(alice), Access::manage()),
1173                    (GroupMember::Individual(alice_laptop), Access::manage()),
1174                    (GroupMember::Individual(alice_mobile), Access::write()),
1175                ],
1176            },
1177        };
1178
1179        // Prepare the operation.
1180        let (devices_group_y, operation_001) =
1181            TestGroup::prepare(devices_group_y, &control_message_001).unwrap();
1182
1183        // Process the operation.
1184        let devices_group_y = TestGroup::process(devices_group_y, &operation_001).unwrap();
1185
1186        // alice, alice_laptop and alice_mobile are all members of the group.
1187        let mut members = devices_group_y.members();
1188        members.sort();
1189        assert_eq!(
1190            members,
1191            vec![
1192                (GroupMember::Individual(alice), Access::manage()),
1193                (GroupMember::Individual(alice_laptop), Access::manage()),
1194                (GroupMember::Individual(alice_mobile), Access::write()),
1195            ],
1196        );
1197
1198        // Create alice's team group, with alice as the only member.
1199        let control_message_002 = GroupControlMessage {
1200            group_id: team_group_y.id(),
1201            action: GroupAction::Create {
1202                initial_members: vec![(GroupMember::Individual(alice), Access::manage())],
1203            },
1204        };
1205
1206        // Prepare the operation.
1207        let (team_group_y, operation_002) =
1208            TestGroup::prepare(team_group_y, &control_message_002).unwrap();
1209
1210        // Process it.
1211        let team_group_y = TestGroup::process(team_group_y, &operation_002).unwrap();
1212
1213        // Add alice's devices group as a member of her teams group with read access.
1214        let control_message_003 = GroupControlMessage {
1215            group_id: team_group_y.id(),
1216            action: GroupAction::Add {
1217                member: GroupMember::Group(devices_group_y.id()),
1218                access: Access::read(),
1219            },
1220        };
1221        let (team_group_y, operation_003) =
1222            TestGroup::prepare(team_group_y, &control_message_003).unwrap();
1223        let team_group_y = TestGroup::process(team_group_y, &operation_003).unwrap();
1224
1225        // Alice and the devices group are direct members of the team group.
1226        let mut members = team_group_y.members();
1227        members.sort();
1228        assert_eq!(
1229            members,
1230            vec![
1231                (GroupMember::Individual(alice), Access::manage()),
1232                (GroupMember::Group(alice_devices_group), Access::read())
1233            ]
1234        );
1235
1236        // alice, alice_laptop and alice_mobile are transitive members, only alice has Manage access
1237        // (even though alice_laptop has Manage access to the devices sub-group).
1238        let mut transitive_members = team_group_y.transitive_members().unwrap();
1239        transitive_members.sort();
1240        assert_eq!(
1241            transitive_members,
1242            vec![
1243                (alice, Access::manage()),
1244                (alice_laptop, Access::read()),
1245                (alice_mobile, Access::read()),
1246            ]
1247        );
1248    }
1249
1250    #[test]
1251    fn multi_user() {
1252        let alice = 'A';
1253        let bob = 'B';
1254        let claire = 'C';
1255
1256        let alice_mobile = 'M';
1257        let alice_laptop = 'L';
1258
1259        let alice_devices_group = 'D';
1260        let alice_team_group = 'T';
1261
1262        let rng = StdRng::from_os_rng();
1263        // let rng = StdRng::from_seed([0u8; 32]);
1264
1265        let mut network = Network::new([alice, bob, claire], rng);
1266
1267        // Alice creates a team group with themselves as initial member.
1268        network.create(
1269            alice_team_group,
1270            alice,
1271            vec![(GroupMember::Individual(alice), Access::manage())],
1272        );
1273
1274        // And then adds bob as manager.
1275        network.add(
1276            alice,
1277            GroupMember::Individual(bob),
1278            alice_team_group,
1279            Access::manage(),
1280        );
1281
1282        // Everyone processes these operations.
1283        network.process();
1284
1285        let alice_members = network.members(&alice, &alice_team_group);
1286        let bob_members = network.members(&bob, &alice_team_group);
1287        let claire_members = network.members(&claire, &alice_team_group);
1288        assert_eq!(
1289            alice_members,
1290            vec![
1291                (GroupMember::Individual('A'), Access::manage()),
1292                (GroupMember::Individual('B'), Access::manage()),
1293            ]
1294        );
1295        assert_eq!(alice_members, claire_members);
1296        assert_eq!(alice_members, bob_members);
1297
1298        let alice_transitive_members = network.transitive_members(&alice, &alice_team_group);
1299        let bob_transitive_members = network.transitive_members(&bob, &alice_team_group);
1300        let claire_transitive_members = network.transitive_members(&claire, &alice_team_group);
1301        assert_eq!(
1302            alice_transitive_members,
1303            vec![('A', Access::manage()), ('B', Access::manage()),]
1304        );
1305        assert_eq!(alice_transitive_members, bob_transitive_members);
1306        assert_eq!(alice_transitive_members, claire_transitive_members);
1307
1308        // Bob adds claire with read access.
1309        network.add(
1310            bob,
1311            GroupMember::Individual(claire),
1312            alice_team_group,
1313            Access::read(),
1314        );
1315
1316        // Alice (concurrently) creates a devices group.
1317        network.create(
1318            alice_devices_group,
1319            alice,
1320            vec![
1321                (GroupMember::Individual(alice_mobile), Access::write()),
1322                (GroupMember::Individual(alice_laptop), Access::manage()),
1323            ],
1324        );
1325
1326        // And adds it to the teams group.
1327        network.add(
1328            alice,
1329            GroupMember::Group(alice_devices_group),
1330            alice_team_group,
1331            Access::manage(),
1332        );
1333
1334        // Everyone processes these operations.
1335        network.process();
1336
1337        let alice_members = network.members(&alice, &alice_team_group);
1338        let bob_members = network.members(&bob, &alice_team_group);
1339        let claire_members = network.members(&claire, &alice_team_group);
1340        assert_eq!(
1341            alice_members,
1342            vec![
1343                (GroupMember::Individual('A'), Access::manage()),
1344                (GroupMember::Individual('B'), Access::manage()),
1345                (GroupMember::Individual('C'), Access::read()),
1346                (GroupMember::Group('D'), Access::manage())
1347            ]
1348        );
1349        assert_eq!(alice_members, bob_members);
1350        assert_eq!(alice_members, claire_members);
1351
1352        let alice_transitive_members = network.transitive_members(&alice, &alice_team_group);
1353        let bob_transitive_members = network.transitive_members(&bob, &alice_team_group);
1354        let claire_transitive_members = network.transitive_members(&claire, &alice_team_group);
1355        assert_eq!(
1356            alice_transitive_members,
1357            vec![
1358                ('A', Access::manage()),
1359                ('B', Access::manage()),
1360                ('C', Access::read()),
1361                ('L', Access::manage()),
1362                ('M', Access::write())
1363            ]
1364        );
1365        assert_eq!(alice_transitive_members, bob_transitive_members);
1366        assert_eq!(alice_transitive_members, claire_transitive_members);
1367    }
1368
1369    #[test]
1370    fn ooo() {
1371        let alice = 'A';
1372        let bob = 'B';
1373        let claire = 'C';
1374
1375        let alice_friends = vec!['D', 'E', 'F'];
1376        let bob_friends = vec!['G', 'H', 'I'];
1377        let claire_friends = vec!['J', 'K', 'L'];
1378
1379        let friends_group = 'T';
1380
1381        let rng = StdRng::from_os_rng();
1382        // let rng = StdRng::from_seed([0u8; 32]);
1383
1384        let mut network = Network::new([alice, bob, claire], rng);
1385
1386        // Alice creates a friends group with themselves as initial member.
1387        network.create(
1388            friends_group,
1389            alice,
1390            vec![
1391                (GroupMember::Individual(alice), Access::manage()),
1392                (GroupMember::Individual(bob), Access::manage()),
1393                (GroupMember::Individual(claire), Access::manage()),
1394            ],
1395        );
1396
1397        network.process();
1398
1399        // alice, bob and claire all concurrently add 3 new friends, then remove one
1400        for friend in &alice_friends {
1401            network.add(
1402                alice,
1403                GroupMember::Individual(*friend),
1404                friends_group,
1405                Access::read(),
1406            );
1407        }
1408
1409        network.remove(
1410            alice,
1411            GroupMember::Individual(alice_friends[0]),
1412            friends_group,
1413        );
1414
1415        for friend in &bob_friends {
1416            network.add(
1417                bob,
1418                GroupMember::Individual(*friend),
1419                friends_group,
1420                Access::read(),
1421            );
1422        }
1423
1424        network.remove(bob, GroupMember::Individual(bob_friends[0]), friends_group);
1425
1426        for friend in &claire_friends {
1427            network.add(
1428                claire,
1429                GroupMember::Individual(*friend),
1430                friends_group,
1431                Access::read(),
1432            );
1433        }
1434
1435        network.remove(
1436            claire,
1437            GroupMember::Individual(claire_friends[0]),
1438            friends_group,
1439        );
1440
1441        // alice, bob and claire all process these messages in random orders.
1442        network.process_ooo();
1443
1444        let alice_members = network.members(&alice, &friends_group);
1445        let bob_members = network.members(&bob, &friends_group);
1446        let claire_members = network.members(&claire, &friends_group);
1447        assert_eq!(
1448            alice_members,
1449            vec![
1450                (GroupMember::Individual('A'), Access::manage()),
1451                (GroupMember::Individual('B'), Access::manage()),
1452                (GroupMember::Individual('C'), Access::manage()),
1453                // (GroupMember::Individual('D'), Access::read()),
1454                (GroupMember::Individual('E'), Access::read()),
1455                (GroupMember::Individual('F'), Access::read()),
1456                // (GroupMember::Individual('G'), Access::read()),
1457                (GroupMember::Individual('H'), Access::read()),
1458                (GroupMember::Individual('I'), Access::read()),
1459                // (GroupMember::Individual('J'), Access::read()),
1460                (GroupMember::Individual('K'), Access::read()),
1461                (GroupMember::Individual('L'), Access::read()),
1462            ]
1463        );
1464        assert_eq!(alice_members, claire_members);
1465        assert_eq!(alice_members, bob_members);
1466    }
1467
1468    #[test]
1469    fn add_remove_add() {
1470        let alice = 'A';
1471        let bob = 'B';
1472
1473        let friends_group = 'T';
1474
1475        let rng = StdRng::from_os_rng();
1476        // let rng = StdRng::from_seed([0u8; 32]);
1477
1478        let mut network = Network::new([alice, bob], rng);
1479
1480        network.create(
1481            friends_group,
1482            alice,
1483            vec![(GroupMember::Individual(alice), Access::manage())],
1484        );
1485
1486        network.add(
1487            alice,
1488            GroupMember::Individual(bob),
1489            friends_group,
1490            Access::read(),
1491        );
1492
1493        network.remove(alice, GroupMember::Individual(bob), friends_group);
1494
1495        let members = network.members(&alice, &friends_group);
1496        assert_eq!(
1497            members,
1498            vec![(GroupMember::Individual('A'), Access::manage()),]
1499        );
1500
1501        network.add(
1502            alice,
1503            GroupMember::Individual(bob),
1504            friends_group,
1505            Access::read(),
1506        );
1507
1508        network.process();
1509
1510        let members = network.members(&alice, &friends_group);
1511        assert_eq!(
1512            members,
1513            vec![
1514                (GroupMember::Individual('A'), Access::manage()),
1515                (GroupMember::Individual('B'), Access::read()),
1516            ]
1517        );
1518    }
1519
1520    const ALICE: char = 'A';
1521    const BOB: char = 'B';
1522    const CHARLIE: char = 'C';
1523    const EDITH: char = 'E';
1524    const BOB_MOBILE: char = 'M';
1525    const BOB_LAPTOP: char = 'L';
1526
1527    const BOB_DEVICES_GROUP: char = 'D';
1528    const CHARLIE_TEAM_GROUP: char = 'T';
1529    const ALICE_ORG_GROUP: char = 'O';
1530
1531    // No concurrency in these test groups, the group store and orderer are shared across all group
1532    // instances.
1533    fn test_groups(rng: StdRng) -> (Network, Vec<MessageId>) {
1534        let mut network = Network::new([ALICE, BOB, CHARLIE], rng);
1535        let mut operations = vec![];
1536
1537        let id = network.create(
1538            BOB_DEVICES_GROUP,
1539            BOB,
1540            vec![
1541                (GroupMember::Individual(BOB), Access::manage()),
1542                (GroupMember::Individual(BOB_LAPTOP), Access::write()),
1543            ],
1544        );
1545        operations.push(id);
1546
1547        let id = network.add(
1548            BOB,
1549            GroupMember::Individual(BOB_MOBILE),
1550            BOB_DEVICES_GROUP,
1551            Access::read(),
1552        );
1553        operations.push(id);
1554
1555        network.process();
1556
1557        let id = network.create(
1558            CHARLIE_TEAM_GROUP,
1559            CHARLIE,
1560            vec![
1561                (GroupMember::Individual(CHARLIE), Access::manage()),
1562                (GroupMember::Individual(EDITH), Access::read()),
1563            ],
1564        );
1565        operations.push(id);
1566
1567        let id = network.create(
1568            ALICE_ORG_GROUP,
1569            ALICE,
1570            vec![(GroupMember::Individual(ALICE), Access::manage())],
1571        );
1572        operations.push(id);
1573
1574        network.process();
1575
1576        let id = network.add(
1577            CHARLIE,
1578            GroupMember::Group(BOB_DEVICES_GROUP),
1579            CHARLIE_TEAM_GROUP,
1580            Access::manage(),
1581        );
1582        operations.push(id);
1583
1584        network.process();
1585
1586        let id = network.add(
1587            ALICE,
1588            GroupMember::Group(CHARLIE_TEAM_GROUP),
1589            ALICE_ORG_GROUP,
1590            Access::write(),
1591        );
1592        operations.push(id);
1593
1594        network.process();
1595
1596        (network, operations)
1597    }
1598
1599    #[test]
1600    fn transitive_members() {
1601        let rng = StdRng::from_os_rng();
1602        let (network, _) = test_groups(rng);
1603
1604        let expected_bob_devices_group_direct_members = vec![
1605            (GroupMember::Individual(BOB), Access::manage()),
1606            (GroupMember::Individual(BOB_LAPTOP), Access::write()),
1607            (GroupMember::Individual(BOB_MOBILE), Access::read()),
1608        ];
1609
1610        let expected_bob_devices_group_transitive_members = vec![
1611            (BOB, Access::manage()),
1612            (BOB_LAPTOP, Access::write()),
1613            (BOB_MOBILE, Access::read()),
1614        ];
1615
1616        let expected_charlie_team_group_direct_members = vec![
1617            (GroupMember::Individual(CHARLIE), Access::manage()),
1618            (GroupMember::Individual(EDITH), Access::read()),
1619            (GroupMember::Group(BOB_DEVICES_GROUP), Access::manage()),
1620        ];
1621
1622        let expected_charlie_team_group_transitive_members = vec![
1623            (BOB, Access::manage()),
1624            (CHARLIE, Access::manage()),
1625            (EDITH, Access::read()),
1626            (BOB_LAPTOP, Access::write()),
1627            (BOB_MOBILE, Access::read()),
1628        ];
1629
1630        let expected_alice_org_group_direct_members = vec![
1631            (GroupMember::Individual(ALICE), Access::manage()),
1632            (GroupMember::Group(CHARLIE_TEAM_GROUP), Access::write()),
1633        ];
1634
1635        let expected_alice_org_group_transitive_members = vec![
1636            (ALICE, Access::manage()),
1637            (BOB, Access::write()),
1638            (CHARLIE, Access::write()),
1639            (EDITH, Access::read()),
1640            (BOB_LAPTOP, Access::write()),
1641            (BOB_MOBILE, Access::read()),
1642        ];
1643
1644        let members = network.members(&BOB, &BOB_DEVICES_GROUP);
1645        assert_eq!(members, expected_bob_devices_group_direct_members);
1646
1647        let transitive_members = network.transitive_members(&BOB, &BOB_DEVICES_GROUP);
1648        assert_eq!(
1649            transitive_members,
1650            expected_bob_devices_group_transitive_members
1651        );
1652
1653        let members = network.members(&CHARLIE, &CHARLIE_TEAM_GROUP);
1654        assert_eq!(members, expected_charlie_team_group_direct_members);
1655
1656        let transitive_members = network.transitive_members(&CHARLIE, &CHARLIE_TEAM_GROUP);
1657        assert_eq!(
1658            transitive_members,
1659            expected_charlie_team_group_transitive_members
1660        );
1661
1662        let members = network.members(&ALICE, &ALICE_ORG_GROUP);
1663        assert_eq!(members, expected_alice_org_group_direct_members);
1664
1665        let transitive_members = network.transitive_members(&ALICE, &ALICE_ORG_GROUP);
1666        assert_eq!(
1667            transitive_members,
1668            expected_alice_org_group_transitive_members
1669        );
1670    }
1671
1672    #[test]
1673    fn members_at() {
1674        let rng = StdRng::from_os_rng();
1675        let (network, operations) = test_groups(rng);
1676
1677        let create_devices_op_id = operations[0];
1678        let add_mobile_to_devices_op_id = operations[1];
1679        let create_team_op_id = operations[2];
1680        let create_org_op_id = operations[3];
1681        let add_devices_to_team_op_id = operations[4];
1682        let add_team_to_org_op_id = operations[5];
1683
1684        // Initial state of the org group.
1685        let members =
1686            network.transitive_members_at(&ALICE, &ALICE_ORG_GROUP, &vec![create_org_op_id]);
1687        assert_eq!(members, vec![(ALICE, Access::manage())]);
1688
1689        // CHARLIE_TEAM was added but before BOB_DEVICES was added to the team.
1690        let members = network.transitive_members_at(
1691            &ALICE,
1692            &ALICE_ORG_GROUP,
1693            &vec![add_team_to_org_op_id, create_team_op_id],
1694        );
1695        assert_eq!(
1696            members,
1697            vec![
1698                (ALICE, Access::manage()),
1699                (CHARLIE, Access::write()),
1700                (EDITH, Access::read())
1701            ]
1702        );
1703
1704        // now BOB_DEVICES was added to the team.
1705        let members = network.transitive_members_at(
1706            &ALICE,
1707            &ALICE_ORG_GROUP,
1708            &vec![
1709                add_team_to_org_op_id,
1710                create_devices_op_id,
1711                add_devices_to_team_op_id,
1712            ],
1713        );
1714        assert_eq!(
1715            members,
1716            vec![
1717                (ALICE, Access::manage()),
1718                (BOB, Access::write()),
1719                (CHARLIE, Access::write()),
1720                (EDITH, Access::read()),
1721                (BOB_LAPTOP, Access::write()),
1722            ]
1723        );
1724
1725        // now BOB_MOBILE was added to the devices group and we are at "current state".
1726        let members_at_most_recent_heads = network.transitive_members_at(
1727            &ALICE,
1728            &ALICE_ORG_GROUP,
1729            &vec![
1730                add_team_to_org_op_id,
1731                add_mobile_to_devices_op_id,
1732                add_devices_to_team_op_id,
1733            ],
1734        );
1735        assert_eq!(
1736            members_at_most_recent_heads,
1737            vec![
1738                (ALICE, Access::manage()),
1739                (BOB, Access::write()),
1740                (CHARLIE, Access::write()),
1741                (EDITH, Access::read()),
1742                (BOB_LAPTOP, Access::write()),
1743                (BOB_MOBILE, Access::read()),
1744            ]
1745        );
1746
1747        // These queries should produce the same "current" member state.
1748        let current_members = network.transitive_members(&ALICE, &ALICE_ORG_GROUP);
1749        // This is a slightly strange thing to do, we are requesting the current state by passing in a
1750        // vec of all known operation ids. Logically it should produce the same state though.
1751        let members_by_all_known_operations =
1752            network.transitive_members_at(&ALICE, &ALICE_ORG_GROUP, &operations);
1753
1754        assert_eq!(members_at_most_recent_heads, current_members);
1755        assert_eq!(
1756            members_at_most_recent_heads,
1757            members_by_all_known_operations
1758        );
1759    }
1760    #[test]
1761    fn error_cases() {
1762        let group_id = '0';
1763        let alice = 'A';
1764        let bob = 'B';
1765        let claire = 'C';
1766        let dave = 'D';
1767        let eve = 'E';
1768
1769        let mut rng = StdRng::from_os_rng();
1770
1771        let (y_i, _) = create_group(
1772            alice,
1773            group_id,
1774            vec![
1775                (alice, Access::manage()),
1776                (bob, Access::read()),
1777                (claire, Access::read()),
1778            ],
1779            &mut rng,
1780        );
1781
1782        let previous: Vec<u32> = y_i.heads().into_iter().collect();
1783
1784        // AlreadyAdded
1785        let op = TestOperation {
1786            id: 1,
1787            author: alice,
1788            dependencies: previous.clone(),
1789            previous: previous.clone(),
1790            payload: GroupControlMessage {
1791                group_id,
1792                action: GroupAction::Add {
1793                    member: GroupMember::Individual(bob),
1794                    access: Access::read(),
1795                },
1796            },
1797        };
1798        assert!(matches!(
1799            GroupCrdt::process(y_i.clone(), &op),
1800            Err(GroupCrdtError::StateChangeError(
1801                _,
1802                GroupMembershipError::AlreadyAdded(GroupMember::Individual('B'))
1803            ))
1804        ));
1805
1806        // Remove claire so we can test AlreadyRemoved
1807        let y_ii = GroupCrdt::process(
1808            y_i,
1809            &TestOperation {
1810                id: 2,
1811                author: alice,
1812                dependencies: previous.clone(),
1813                previous: previous.clone(),
1814                payload: GroupControlMessage {
1815                    group_id,
1816                    action: GroupAction::Remove {
1817                        member: GroupMember::Individual(claire),
1818                    },
1819                },
1820            },
1821        )
1822        .unwrap();
1823
1824        let previous: Vec<u32> = y_ii.heads().into_iter().collect();
1825
1826        // AlreadyRemoved
1827        let op = TestOperation {
1828            id: 3,
1829            author: alice,
1830            dependencies: previous.clone(),
1831            previous: previous.clone(),
1832            payload: GroupControlMessage {
1833                group_id,
1834                action: GroupAction::Remove {
1835                    member: GroupMember::Individual(claire),
1836                },
1837            },
1838        };
1839        assert!(matches!(
1840            GroupCrdt::process(y_ii.clone(), &op),
1841            Err(GroupCrdtError::StateChangeError(
1842                _,
1843                GroupMembershipError::AlreadyRemoved(GroupMember::Individual('C'))
1844            ))
1845        ));
1846
1847        // InsufficientAccess
1848        let op = TestOperation {
1849            id: 4,
1850            author: bob,
1851            dependencies: previous.clone(),
1852            previous: previous.clone(),
1853            payload: GroupControlMessage {
1854                group_id,
1855                action: GroupAction::Add {
1856                    member: GroupMember::Individual(dave),
1857                    access: Access::read(),
1858                },
1859            },
1860        };
1861        assert!(matches!(
1862            GroupCrdt::process(y_ii.clone(), &op),
1863            Err(GroupCrdtError::StateChangeError(
1864                _,
1865                GroupMembershipError::InsufficientAccess(GroupMember::Individual('B'))
1866            ))
1867        ));
1868
1869        // Remove bob so we can test InactiveActor
1870        let y_iii = GroupCrdt::process(
1871            y_ii,
1872            &TestOperation {
1873                id: 5,
1874                author: alice,
1875                dependencies: previous.clone(),
1876                previous: previous.clone(),
1877                payload: GroupControlMessage {
1878                    group_id,
1879                    action: GroupAction::Remove {
1880                        member: GroupMember::Individual(bob),
1881                    },
1882                },
1883            },
1884        )
1885        .unwrap();
1886
1887        let previous: Vec<u32> = y_iii.heads().into_iter().collect();
1888
1889        // InactiveActor
1890        let op = TestOperation {
1891            id: 6,
1892            author: bob,
1893            dependencies: previous.clone(),
1894            previous: previous.clone(),
1895            payload: GroupControlMessage {
1896                group_id,
1897                action: GroupAction::Add {
1898                    member: GroupMember::Individual(dave),
1899                    access: Access::read(),
1900                },
1901            },
1902        };
1903        assert!(matches!(
1904            GroupCrdt::process(y_iii.clone(), &op),
1905            Err(GroupCrdtError::StateChangeError(
1906                _,
1907                GroupMembershipError::InactiveActor(GroupMember::Individual('B'))
1908            ))
1909        ));
1910
1911        // InactiveMember
1912        let op = TestOperation {
1913            id: 7,
1914            author: alice,
1915            dependencies: previous.clone(),
1916            previous: previous.clone(),
1917            payload: GroupControlMessage {
1918                group_id,
1919                action: GroupAction::Promote {
1920                    member: GroupMember::Individual(claire),
1921                    access: Access::write(),
1922                },
1923            },
1924        };
1925        assert!(matches!(
1926            GroupCrdt::process(y_iii.clone(), &op),
1927            Err(GroupCrdtError::StateChangeError(
1928                _,
1929                GroupMembershipError::InactiveMember(GroupMember::Individual('C'))
1930            ))
1931        ));
1932
1933        // UnrecognisedActor
1934        let op = TestOperation {
1935            id: 8,
1936            author: eve,
1937            dependencies: previous.clone(),
1938            previous: previous.clone(),
1939            payload: GroupControlMessage {
1940                group_id,
1941                action: GroupAction::Add {
1942                    member: GroupMember::Individual(dave),
1943                    access: Access::read(),
1944                },
1945            },
1946        };
1947        assert!(matches!(
1948            GroupCrdt::process(y_iii.clone(), &op),
1949            Err(GroupCrdtError::StateChangeError(
1950                _,
1951                GroupMembershipError::UnrecognisedActor(GroupMember::Individual('E'))
1952            ))
1953        ));
1954
1955        // UnrecognisedMember
1956        let op = TestOperation {
1957            id: 9,
1958            author: alice,
1959            dependencies: previous.clone(),
1960            previous: previous.clone(),
1961            payload: GroupControlMessage {
1962                group_id,
1963                action: GroupAction::Promote {
1964                    member: GroupMember::Individual(eve),
1965                    access: Access::write(),
1966                },
1967            },
1968        };
1969        assert!(matches!(
1970            GroupCrdt::process(y_iii.clone(), &op),
1971            Err(GroupCrdtError::StateChangeError(
1972                _,
1973                GroupMembershipError::UnrecognisedMember(GroupMember::Individual('E'))
1974            ))
1975        ));
1976    }
1977    #[test]
1978    fn error_cases_resolver() {
1979        let group_id = '0';
1980        let alice = 'A';
1981        let bob = 'B';
1982        let claire = 'C';
1983        let dave = 'D';
1984        let eve = 'E';
1985
1986        let mut rng = StdRng::from_os_rng();
1987
1988        let (y_i, _) = create_group(
1989            alice,
1990            group_id,
1991            vec![
1992                (alice, Access::manage()),
1993                (bob, Access::read()),
1994                (claire, Access::read()),
1995            ],
1996            &mut rng,
1997        );
1998
1999        let previous: Vec<u32> = y_i.heads().into_iter().collect();
2000
2001        // Remove all current members and all all non-members as managers in a concurrent branch.
2002        let (mut y_ii, _) = remove_member(y_i, group_id, bob);
2003        (y_ii, _) = remove_member(y_ii, group_id, claire);
2004        (y_ii, _) = add_member(y_ii, group_id, dave, Access::manage());
2005        (y_ii, _) = add_member(y_ii, group_id, eve, Access::manage());
2006        (y_ii, _) = remove_member(y_ii, group_id, alice);
2007
2008        let mut members = y_ii.members();
2009        members.sort();
2010        assert_eq!(
2011            members,
2012            vec![
2013                (GroupMember::Individual(dave), Access::manage()),
2014                (GroupMember::Individual(eve), Access::manage())
2015            ]
2016        );
2017
2018        // All the following operations are appended into the group operation graph into a branch
2019        // concurrent to all the previous group changes. This means they should be validated against
2020        // state which does not include those changes (even though they are the "current" state).
2021
2022        // AlreadyAdded (bob)
2023        let op = TestOperation {
2024            id: 1,
2025            author: alice,
2026            dependencies: previous.clone(),
2027            previous: previous.clone(),
2028            payload: GroupControlMessage {
2029                group_id,
2030                action: GroupAction::Add {
2031                    member: GroupMember::Individual(bob),
2032                    access: Access::read(),
2033                },
2034            },
2035        };
2036        assert!(matches!(
2037            GroupCrdt::process(y_ii.clone(), &op),
2038            Err(GroupCrdtError::StateChangeError(
2039                _,
2040                GroupMembershipError::AlreadyAdded(GroupMember::Individual('B'))
2041            ))
2042        ));
2043
2044        // Remove claire
2045        let op = TestOperation {
2046            id: 2,
2047            author: alice,
2048            dependencies: previous.clone(),
2049            previous: previous.clone(),
2050            payload: GroupControlMessage {
2051                group_id,
2052                action: GroupAction::Remove {
2053                    member: GroupMember::Individual(claire),
2054                },
2055            },
2056        };
2057        let y_iii = GroupCrdt::process(y_ii.clone(), &op).unwrap();
2058
2059        // Refer to only the newly published operation in previous so as to remain in the concurrent branch.
2060        let previous = vec![op.id];
2061
2062        // AlreadyRemoved (claire)
2063        let op = TestOperation {
2064            id: 3,
2065            author: alice,
2066            dependencies: previous.clone(),
2067            previous: previous.clone(),
2068            payload: GroupControlMessage {
2069                group_id,
2070                action: GroupAction::Remove {
2071                    member: GroupMember::Individual(claire),
2072                },
2073            },
2074        };
2075        assert!(matches!(
2076            GroupCrdt::process(y_iii.clone(), &op),
2077            Err(GroupCrdtError::StateChangeError(
2078                _,
2079                GroupMembershipError::AlreadyRemoved(GroupMember::Individual('C'))
2080            ))
2081        ));
2082
2083        // InsufficientAccess (bob tries to add dave)
2084        let op = TestOperation {
2085            id: 4,
2086            author: bob,
2087            dependencies: previous.clone(),
2088            previous: previous.clone(),
2089            payload: GroupControlMessage {
2090                group_id,
2091                action: GroupAction::Add {
2092                    member: GroupMember::Individual(dave),
2093                    access: Access::read(),
2094                },
2095            },
2096        };
2097        assert!(matches!(
2098            GroupCrdt::process(y_iii.clone(), &op),
2099            Err(GroupCrdtError::StateChangeError(
2100                _,
2101                GroupMembershipError::InsufficientAccess(GroupMember::Individual('B'))
2102            ))
2103        ));
2104
2105        // Remove bob
2106        let op = TestOperation {
2107            id: 5,
2108            author: alice,
2109            dependencies: previous.clone(),
2110            previous: previous.clone(),
2111            payload: GroupControlMessage {
2112                group_id,
2113                action: GroupAction::Remove {
2114                    member: GroupMember::Individual(bob),
2115                },
2116            },
2117        };
2118        let y_iv = GroupCrdt::process(y_iii.clone(), &op).unwrap();
2119
2120        // Refer to only the newly published operation in previous so as to remain in the concurrent branch.
2121        let previous = vec![op.id];
2122
2123        // InactiveActor (bob tries to add dave)
2124        let op = TestOperation {
2125            id: 6,
2126            author: bob,
2127            dependencies: previous.clone(),
2128            previous: previous.clone(),
2129            payload: GroupControlMessage {
2130                group_id,
2131                action: GroupAction::Add {
2132                    member: GroupMember::Individual(dave),
2133                    access: Access::read(),
2134                },
2135            },
2136        };
2137        assert!(matches!(
2138            GroupCrdt::process(y_iv.clone(), &op),
2139            Err(GroupCrdtError::StateChangeError(
2140                _,
2141                GroupMembershipError::InactiveActor(GroupMember::Individual('B'))
2142            ))
2143        ));
2144
2145        // InactiveMember (claire promoted)
2146        let op = TestOperation {
2147            id: 7,
2148            author: alice,
2149            dependencies: previous.clone(),
2150            previous: previous.clone(),
2151            payload: GroupControlMessage {
2152                group_id,
2153                action: GroupAction::Promote {
2154                    member: GroupMember::Individual(claire),
2155                    access: Access::write(),
2156                },
2157            },
2158        };
2159        assert!(matches!(
2160            GroupCrdt::process(y_iv.clone(), &op),
2161            Err(GroupCrdtError::StateChangeError(
2162                _,
2163                GroupMembershipError::InactiveMember(GroupMember::Individual('C'))
2164            ))
2165        ));
2166
2167        // UnrecognisedActor (eve tries to add dave)
2168        let op = TestOperation {
2169            id: 8,
2170            author: eve,
2171            dependencies: previous.clone(),
2172            previous: previous.clone(),
2173            payload: GroupControlMessage {
2174                group_id,
2175                action: GroupAction::Add {
2176                    member: GroupMember::Individual(dave),
2177                    access: Access::read(),
2178                },
2179            },
2180        };
2181        assert!(matches!(
2182            GroupCrdt::process(y_iv.clone(), &op),
2183            Err(GroupCrdtError::StateChangeError(
2184                _,
2185                GroupMembershipError::UnrecognisedActor(GroupMember::Individual('E'))
2186            ))
2187        ));
2188
2189        // UnrecognisedMember (alice promotes eve)
2190        let op = TestOperation {
2191            id: 9,
2192            author: alice,
2193            dependencies: previous.clone(),
2194            previous: previous.clone(),
2195            payload: GroupControlMessage {
2196                group_id,
2197                action: GroupAction::Promote {
2198                    member: GroupMember::Individual(eve),
2199                    access: Access::write(),
2200                },
2201            },
2202        };
2203        assert!(matches!(
2204            GroupCrdt::process(y_iv.clone(), &op),
2205            Err(GroupCrdtError::StateChangeError(
2206                _,
2207                GroupMembershipError::UnrecognisedMember(GroupMember::Individual('E'))
2208            ))
2209        ));
2210    }
2211}