p2panda_encryption/message_scheme/test_utils/
dgm.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::collections::{HashMap, HashSet};
4use std::fmt::Debug;
5use std::marker::PhantomData;
6
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::traits::{AckedGroupMembership, IdentityHandle, OperationId};
11
12/// Non-optimal "Acked" Decentralised Group Membership CRDT implementation for p2panda's
13/// message encryption scheme.
14///
15/// This does not support re-adding a member, is only used for testing and will be soon
16/// replaced with an optimal implementation.
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct AckedTestDgm<ID, OP> {
19    _marker: PhantomData<(ID, OP)>,
20}
21
22impl<ID, OP> AckedTestDgm<ID, OP>
23where
24    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
25    OP: OperationId + Serialize + for<'a> Deserialize<'a>,
26{
27    pub fn init(my_id: ID) -> State<ID, OP> {
28        State {
29            my_id,
30            members: HashSet::new(),
31            removed_members: HashSet::new(),
32            infos: HashMap::new(),
33            remove_infos: HashMap::new(),
34            adds_by_msg: HashMap::new(),
35            removes_by_msg: HashSet::new(),
36        }
37    }
38}
39
40#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct State<ID, OP>
42where
43    ID: IdentityHandle,
44    OP: OperationId,
45{
46    my_id: ID,
47    members: HashSet<ID>,
48    removed_members: HashSet<ID>,
49    infos: HashMap<ID, MemberInfo<ID, OP>>,
50    remove_infos: HashMap<OP, RemoveInfo<ID>>,
51    adds_by_msg: HashMap<OP, ID>,
52    removes_by_msg: HashSet<OP>,
53}
54
55impl<ID, OP> AckedGroupMembership<ID, OP> for AckedTestDgm<ID, OP>
56where
57    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
58    OP: OperationId + Serialize + for<'a> Deserialize<'a>,
59{
60    type State = State<ID, OP>;
61
62    type Error = TestAckedGroupError<ID, OP>;
63
64    fn from_welcome(
65        mut y: Self::State,
66        y_welcome: Self::State,
67    ) -> Result<Self::State, Self::Error> {
68        // TODO: This does not handle scenarios well when the same members get concurrently added.
69        // Merge states.
70        y.members.extend(y_welcome.members);
71        y.removed_members.extend(y_welcome.removed_members);
72        y.infos.extend(y_welcome.infos);
73        y.remove_infos.extend(y_welcome.remove_infos);
74        y.adds_by_msg.extend(y_welcome.adds_by_msg);
75        y.removes_by_msg.extend(y_welcome.removes_by_msg);
76        Ok(y)
77    }
78
79    fn create(my_id: ID, initial_members: &[ID]) -> Result<Self::State, Self::Error> {
80        let mut initial_members = initial_members.to_vec();
81        if !initial_members.contains(&my_id) {
82            initial_members.push(my_id);
83        }
84
85        let mut infos = HashMap::with_capacity(initial_members.len());
86        let mut members = HashSet::with_capacity(initial_members.len());
87
88        for member in &initial_members {
89            infos.insert(*member, MemberInfo::new(*member, None, &initial_members));
90            members.insert(*member);
91        }
92
93        Ok(Self::State {
94            my_id,
95            members,
96            removed_members: HashSet::new(),
97            infos,
98            remove_infos: HashMap::new(),
99            adds_by_msg: HashMap::new(),
100            removes_by_msg: HashSet::new(),
101        })
102    }
103
104    /// Handles message adding a new member ("added") to the group by another member ("adder").
105    /// Please note that a user can only be added to a group once.
106    fn add(
107        mut y: Self::State,
108        adder: ID,
109        added: ID,
110        message_id: OP,
111    ) -> Result<Self::State, Self::Error> {
112        let mut added_info = MemberInfo::new(added, Some(adder), &[]);
113        added_info.acks.insert(adder);
114        added_info.acks.insert(added);
115        added_info.acks.insert(y.my_id);
116
117        // Is `actor` still a member of the group itself?
118        if y.members.contains(&adder) {
119            // TODO: How to handle adds when the member already exists? This de-duplicates the
120            // member, but overwrites the `added_info` with a new state?
121            y.members.insert(added);
122            y.infos.insert(added, added_info);
123        } else {
124            // `actor` has been removed by a remove messages concurrent to this add message.
125            // All the remove messages removing `actor` get credit for removing `added` as
126            // well.
127            let actor_info = y
128                .infos
129                .get_mut(&adder)
130                .ok_or(TestAckedGroupError::UnrecognizedMember(adder))?;
131            for remove_message_id in &actor_info.remove_messages {
132                let remove_info = y
133                    .remove_infos
134                    .get_mut(remove_message_id)
135                    .expect("remove_infos values should be consistent with remove_messages");
136                remove_info.removed.insert(added);
137                added_info.remove_messages.push(*remove_message_id);
138            }
139            y.removed_members.insert(added);
140            y.infos.insert(added, added_info);
141        }
142
143        // If `actor` acknowledged adding or removing a member in the past, then we can be sure
144        // that `added` also acknowledges it as they must have been made aware of this history
145        // by receiving the welcome message from `actor`.
146        for member in &y.members {
147            let member_info = y
148                .infos
149                .get_mut(member)
150                .expect("infos values should be consistent with members keys");
151            if member_info.acks.contains(&adder) {
152                member_info.acks.insert(added);
153            }
154        }
155
156        for member in &y.removed_members {
157            let member_info = y
158                .infos
159                .get_mut(member)
160                .expect("infos values should be consistent with removed_members keys");
161            if member_info.acks.contains(&adder) {
162                member_info.acks.insert(added);
163            }
164        }
165
166        for message_id in &y.removes_by_msg {
167            let remove_info = y
168                .remove_infos
169                .get_mut(message_id)
170                .expect("remove_infos values should be consistent with removes_by_msg keys");
171            if remove_info.acks.contains(&adder) {
172                remove_info.acks.insert(added);
173            }
174        }
175
176        y.adds_by_msg.insert(message_id, added);
177
178        Ok(y)
179    }
180
181    fn remove(
182        mut y: Self::State,
183        remover: ID,
184        removed: &ID,
185        message_id: OP,
186    ) -> Result<Self::State, Self::Error> {
187        let removed: &[ID] = &[*removed];
188
189        let mut remove_result = Vec::new();
190
191        let mut remove_info = RemoveInfo::new(removed);
192        remove_info.acks.insert(remover);
193        remove_info.acks.insert(y.my_id);
194
195        // Remove the users in removed (if needed) and mark them as removed by this message.
196        for removed_member in removed {
197            let has_removed_member = y.members.remove(removed_member);
198            if has_removed_member {
199                let member_info = y
200                    .infos
201                    .get_mut(removed_member)
202                    .expect("infos values should be consistent with members keys");
203                y.removed_members.insert(*removed_member);
204                member_info.remove_messages.push(message_id);
205                remove_result.push(*removed_member);
206            } else if y.removed_members.contains(removed_member) {
207                if let Some(member_info) = y.infos.get_mut(removed_member) {
208                    // Member has already been removed.
209                    member_info.remove_messages.push(message_id);
210                } else {
211                    return Err(TestAckedGroupError::UnrecognizedMember(*removed_member));
212                }
213            } else {
214                return Err(TestAckedGroupError::UnrecognizedMember(*removed_member));
215            }
216        }
217
218        y.removes_by_msg.insert(message_id);
219        y.remove_infos.insert(message_id, remove_info);
220
221        // If a removed user performed an add concurrent to this message (i.e., not yet ack'd by
222        // actor), then the user added by that message is also considered removed by this
223        // message. This loop searches for such adds and removes their target.
224        //
225        // Since users removed in this fashion may themselves have added users, we have to apply
226        // this rule repeatedly until it stops making progress.
227        loop {
228            let remove_info = y
229                .remove_infos
230                .get_mut(&message_id)
231                .expect("infos values should be consistent with members keys");
232
233            let mut made_progress = false;
234
235            for member in &y.members {
236                let member_info = y
237                    .infos
238                    .get_mut(member)
239                    .expect("infos values should be consistent with members keys");
240                let contains = member_info
241                    .actor
242                    .is_some_and(|actor| remove_info.removed.contains(&actor));
243                if contains && !member_info.acks.contains(&remover) {
244                    remove_result.push(*member);
245                    y.removed_members.insert(*member);
246                    member_info.remove_messages.push(message_id);
247                    remove_info.removed.insert(*member);
248                    made_progress = true;
249                }
250            }
251
252            for removed_member in &remove_result {
253                y.members.remove(removed_member);
254            }
255
256            // Loop through already removed users, adding this message to their list of remove
257            // messages if it applies.
258            for member in &y.removed_members {
259                let member_info = y
260                    .infos
261                    .get_mut(member)
262                    .expect("infos values should be consistent with members keys");
263                let contains = member_info
264                    .actor
265                    .is_some_and(|actor| remove_info.removed.contains(&actor));
266                if contains
267                    && member_info.acks.contains(&remover)
268                    && member_info.remove_messages.contains(&message_id)
269                {
270                    remove_info.removed.insert(*member);
271                    made_progress = true;
272                }
273            }
274
275            if !made_progress {
276                break;
277            }
278        }
279
280        Ok(y)
281    }
282
283    fn members_view(y: &Self::State, viewer: &ID) -> Result<HashSet<ID>, Self::Error> {
284        if viewer.eq(&y.my_id) {
285            return Ok(y.members.clone());
286        }
287
288        let mut view = HashSet::new();
289
290        // Include current members whose add was acked by viewer.
291        for member in &y.members {
292            let member_info = y
293                .infos
294                .get(member)
295                .expect("infos values should be consistent with members keys");
296            if member_info.acks.contains(viewer) {
297                view.insert(*member);
298            }
299        }
300
301        // Also include removed members, none of whose removes have been acked by viewer.
302        for member in &y.removed_members {
303            let member_info = y
304                .infos
305                .get(member)
306                .expect("infos values should be consistent with removed_members keys");
307            let any_acked = member_info.remove_messages.iter().any(|message_id| {
308                let remove_info = y
309                    .remove_infos
310                    .get(message_id)
311                    .expect("remove_infos values should be consistent with remove_messages");
312                remove_info.acks.contains(viewer)
313            });
314            if !any_acked {
315                // Add the member to the view if the removal was not acked yet.
316                //
317                // It's possible that "removed_members" contains transitive removals (Charlie added
318                // Bob, but Alice removed Charlie, so we'll end up with Charlie AND Bob in the
319                // "removed_members" set).
320                //
321                // The viewer might still not have recognized that add of Bob, so we still need to
322                // check if they acked the "add" itself:
323                if member_info.acks.contains(viewer) {
324                    view.insert(*member);
325                }
326            }
327        }
328
329        Ok(view)
330    }
331
332    fn ack(mut y: Self::State, acker: ID, message_id: OP) -> Result<Self::State, Self::Error> {
333        let added = y.adds_by_msg.get_mut(&message_id);
334        match added {
335            Some(added) => {
336                let member_info = y
337                    .infos
338                    .get_mut(added)
339                    .expect("adds_by_msg values should be consistent with members keys");
340                member_info.acks.insert(acker);
341            }
342            None => {
343                let remove_info = y.remove_infos.get_mut(&message_id);
344                match remove_info {
345                    Some(remove_info) => {
346                        if !remove_info.acks.insert(acker) {
347                            return Err(TestAckedGroupError::AlreadyAcked);
348                        }
349
350                        if remove_info.removed.contains(&acker) {
351                            return Err(TestAckedGroupError::AckingOwnRemoval);
352                        }
353                    }
354                    None => return Err(TestAckedGroupError::UnknownMessage(message_id)),
355                }
356            }
357        }
358
359        Ok(y)
360    }
361
362    fn is_add(y: &Self::State, message_id: OP) -> bool {
363        y.adds_by_msg.contains_key(&message_id)
364    }
365
366    fn is_remove(y: &Self::State, message_id: OP) -> bool {
367        y.removes_by_msg.contains(&message_id)
368    }
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
372struct MemberInfo<ID, OP>
373where
374    ID: IdentityHandle,
375{
376    pub id: ID,
377
378    /// Who added this member.
379    pub actor: Option<ID>,
380
381    /// Remove messages that removed this member.
382    pub remove_messages: Vec<OP>,
383
384    /// Users who have ack'd the message.
385    pub acks: HashSet<ID>,
386}
387
388impl<ID, OP> MemberInfo<ID, OP>
389where
390    ID: IdentityHandle,
391    OP: OperationId,
392{
393    fn new(id: ID, actor: Option<ID>, initial_acks: &[ID]) -> Self {
394        let mut acks = HashSet::with_capacity(initial_acks.len());
395        for ack in initial_acks {
396            acks.insert(*ack);
397        }
398
399        Self {
400            id,
401            actor,
402            remove_messages: Vec::new(),
403            acks,
404        }
405    }
406}
407
408#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
409struct RemoveInfo<ID>
410where
411    ID: IdentityHandle,
412{
413    /// Users removed by this message, including users who would have been removed except they were
414    /// removed previously.
415    pub removed: HashSet<ID>,
416
417    /// Users who have ack'd the member.
418    pub acks: HashSet<ID>,
419}
420
421impl<ID> RemoveInfo<ID>
422where
423    ID: IdentityHandle,
424{
425    pub fn new(removed_members: &[ID]) -> Self {
426        let mut removed = HashSet::with_capacity(removed_members.len());
427        for member in removed_members {
428            removed.insert(*member);
429        }
430
431        Self {
432            removed,
433            acks: HashSet::new(),
434        }
435    }
436}
437
438#[derive(Debug, Error)]
439pub enum TestAckedGroupError<ID, OP> {
440    #[error("tried to access unrecognized member")]
441    UnrecognizedMember(ID),
442
443    #[error("already acked")]
444    AlreadyAcked,
445
446    #[error("member acking their own removal")]
447    AckingOwnRemoval,
448
449    #[error("message not recognized")]
450    UnknownMessage(OP),
451}
452
453#[cfg(test)]
454mod tests {
455    use std::collections::HashSet;
456
457    use crate::test_utils::MessageId;
458    use crate::traits::AckedGroupMembership;
459
460    use super::AckedTestDgm;
461
462    #[test]
463    fn concurrent_operations() {
464        let alice = 0;
465        let bob = 1;
466        let charlie = 2;
467        let daphne = 3;
468
469        // Charlie creates a group (charlie: seq=0 "create").
470
471        let charlie_y = AckedTestDgm::create(charlie, &[charlie]).unwrap();
472
473        // Charlie adds Alice (charlie: seq=1 "add").
474
475        let charlie_y = AckedTestDgm::add(
476            charlie_y,
477            charlie,
478            alice,
479            MessageId {
480                sender: charlie,
481                seq: 1,
482            },
483        )
484        .unwrap();
485
486        // Alice processes the "add" of Charlie (alice: seq=0 "ack").
487
488        let alice_y = AckedTestDgm::init(alice);
489        let alice_y = AckedTestDgm::from_welcome(alice_y, charlie_y.clone()).unwrap();
490
491        // Charlie processes Alice's ack.
492
493        let charlie_y = AckedTestDgm::ack(
494            charlie_y,
495            alice,
496            MessageId {
497                sender: charlie,
498                seq: 1,
499            },
500        )
501        .unwrap();
502
503        // They have the same view on the group.
504
505        for id in [alice, charlie] {
506            assert_eq!(
507                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
508                HashSet::from([alice, charlie])
509            );
510
511            assert_eq!(
512                AckedTestDgm::members_view(&alice_y, &id).unwrap(),
513                HashSet::from([alice, charlie])
514            );
515        }
516
517        // -----------------
518
519        // Charlie adds Daphne (charlie: seq=2 "add").
520
521        let charlie_y = AckedTestDgm::add(
522            charlie_y,
523            charlie,
524            daphne,
525            MessageId {
526                sender: charlie,
527                seq: 2,
528            },
529        )
530        .unwrap();
531
532        assert_eq!(
533            AckedTestDgm::members_view(&charlie_y, &charlie).unwrap(),
534            HashSet::from([alice, charlie, daphne])
535        );
536
537        // Daphne processes their add (daphne: seq=0 "ack").
538
539        let daphne_y = AckedTestDgm::init(daphne);
540        let daphne_y = AckedTestDgm::from_welcome(daphne_y, charlie_y.clone()).unwrap();
541
542        assert_eq!(
543            AckedTestDgm::members_view(&daphne_y, &daphne).unwrap(),
544            HashSet::from([alice, charlie, daphne])
545        );
546
547        // Alice processes Charlie's "add" of Daphne (alice: seq=1 "ack").
548
549        let alice_y = AckedTestDgm::add(
550            alice_y,
551            charlie,
552            daphne,
553            MessageId {
554                sender: charlie,
555                seq: 2,
556            },
557        )
558        .unwrap();
559
560        assert_eq!(
561            AckedTestDgm::members_view(&alice_y, &alice).unwrap(),
562            HashSet::from([alice, charlie, daphne])
563        );
564
565        // Everyone processes each other's acks.
566
567        let charlie_y = AckedTestDgm::ack(
568            charlie_y,
569            daphne,
570            MessageId {
571                sender: charlie,
572                seq: 2,
573            },
574        )
575        .unwrap();
576        let alice_y = AckedTestDgm::ack(
577            alice_y,
578            daphne,
579            MessageId {
580                sender: charlie,
581                seq: 2,
582            },
583        )
584        .unwrap();
585        let charlie_y = AckedTestDgm::ack(
586            charlie_y,
587            alice,
588            MessageId {
589                sender: charlie,
590                seq: 2,
591            },
592        )
593        .unwrap();
594        let daphne_y = AckedTestDgm::ack(
595            daphne_y,
596            alice,
597            MessageId {
598                sender: charlie,
599                seq: 2,
600            },
601        )
602        .unwrap();
603
604        // Everyone should have the same members views.
605
606        for id in [alice, charlie, daphne] {
607            assert_eq!(
608                AckedTestDgm::members_view(&alice_y, &id).unwrap(),
609                HashSet::from([alice, charlie, daphne])
610            );
611
612            assert_eq!(
613                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
614                HashSet::from([alice, charlie, daphne])
615            );
616
617            assert_eq!(
618                AckedTestDgm::members_view(&daphne_y, &id).unwrap(),
619                HashSet::from([alice, charlie, daphne])
620            );
621        }
622
623        // ----------------------
624
625        // Alice removes Charlie (alice: seq=2 "remove").
626
627        let alice_y = AckedTestDgm::remove(
628            alice_y,
629            alice,
630            &charlie,
631            MessageId {
632                sender: alice,
633                seq: 2,
634            },
635        )
636        .unwrap();
637
638        assert_eq!(
639            AckedTestDgm::members_view(&alice_y, &alice).unwrap(),
640            HashSet::from([alice, daphne])
641        );
642
643        // Charlie adds Bob concurrently (charlie: seq=3 "add").
644
645        let charlie_y = AckedTestDgm::add(
646            charlie_y,
647            charlie,
648            bob,
649            MessageId {
650                sender: charlie,
651                seq: 3,
652            },
653        )
654        .unwrap();
655
656        for id in [bob, charlie] {
657            assert_eq!(
658                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
659                HashSet::from([alice, bob, charlie, daphne])
660            );
661        }
662
663        for id in [alice, daphne] {
664            assert_eq!(
665                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
666                HashSet::from([alice, charlie, daphne])
667            );
668        }
669
670        // Bob processes their addition.
671
672        let bob_y = AckedTestDgm::init(bob);
673        let bob_y = AckedTestDgm::from_welcome(bob_y, charlie_y.clone()).unwrap();
674
675        for id in [bob, charlie] {
676            assert_eq!(
677                AckedTestDgm::members_view(&bob_y, &id).unwrap(),
678                HashSet::from([alice, bob, charlie, daphne])
679            );
680        }
681
682        // Everyone processes the removal of Charlie.
683
684        let bob_y = AckedTestDgm::remove(
685            bob_y,
686            alice,
687            &charlie,
688            MessageId {
689                sender: alice,
690                seq: 2,
691            },
692        )
693        .unwrap();
694
695        let charlie_y = AckedTestDgm::remove(
696            charlie_y,
697            alice,
698            &charlie,
699            MessageId {
700                sender: alice,
701                seq: 2,
702            },
703        )
704        .unwrap();
705
706        let daphne_y = AckedTestDgm::remove(
707            daphne_y,
708            alice,
709            &charlie,
710            MessageId {
711                sender: alice,
712                seq: 2,
713            },
714        )
715        .unwrap();
716
717        assert_eq!(
718            AckedTestDgm::members_view(&bob_y, &bob).unwrap(),
719            HashSet::from([alice, daphne])
720        );
721
722        assert_eq!(
723            AckedTestDgm::members_view(&charlie_y, &charlie).unwrap(),
724            HashSet::from([alice, daphne])
725        );
726
727        assert_eq!(
728            AckedTestDgm::members_view(&daphne_y, &daphne).unwrap(),
729            HashSet::from([alice, daphne])
730        );
731
732        // Everyone else processes the add of Bob.
733
734        let alice_y = AckedTestDgm::add(
735            alice_y,
736            charlie,
737            bob,
738            MessageId {
739                sender: charlie,
740                seq: 3,
741            },
742        )
743        .unwrap();
744
745        let daphne_y = AckedTestDgm::add(
746            daphne_y,
747            charlie,
748            bob,
749            MessageId {
750                sender: charlie,
751                seq: 3,
752            },
753        )
754        .unwrap();
755
756        // Because of the strong removal CRDT, Bob's add will not be recognized as the adder
757        // Charlie got removed by Alice.
758
759        assert_eq!(
760            AckedTestDgm::members_view(&alice_y, &alice).unwrap(),
761            HashSet::from([alice, daphne])
762        );
763
764        assert_eq!(
765            AckedTestDgm::members_view(&charlie_y, &charlie).unwrap(),
766            HashSet::from([alice, daphne])
767        );
768
769        assert_eq!(
770            AckedTestDgm::members_view(&daphne_y, &daphne).unwrap(),
771            HashSet::from([alice, daphne])
772        );
773
774        // Since nothing was ack'ed yet, all members should believe that the other's didn't do any
775        // changes to their member views yet.
776
777        assert_eq!(
778            AckedTestDgm::members_view(&alice_y, &daphne).unwrap(),
779            HashSet::from([alice, charlie, daphne])
780        );
781
782        assert_eq!(
783            AckedTestDgm::members_view(&bob_y, &alice).unwrap(),
784            HashSet::from([alice, daphne])
785        );
786
787        assert_eq!(
788            AckedTestDgm::members_view(&bob_y, &charlie).unwrap(),
789            HashSet::from([alice, bob, charlie, daphne])
790        );
791
792        assert_eq!(
793            AckedTestDgm::members_view(&bob_y, &daphne).unwrap(),
794            HashSet::from([alice, charlie, daphne])
795        );
796
797        assert_eq!(
798            AckedTestDgm::members_view(&charlie_y, &daphne).unwrap(),
799            HashSet::from([alice, charlie, daphne])
800        );
801    }
802
803    #[test]
804    fn members_view() {
805        let alice = 0;
806        let bob = 1;
807        let charlie = 2;
808
809        // Alice creates a group with Charlie.
810
811        let alice_y = AckedTestDgm::create(alice, &[alice, charlie]).unwrap();
812
813        let charlie_y = AckedTestDgm::init(charlie);
814        let charlie_y = AckedTestDgm::from_welcome(charlie_y, alice_y.clone()).unwrap();
815
816        // Alice adds Bob.
817
818        let alice_y = AckedTestDgm::add(
819            alice_y,
820            alice,
821            bob,
822            MessageId {
823                sender: alice,
824                seq: 1,
825            },
826        )
827        .unwrap();
828
829        let bob_y = AckedTestDgm::init(bob);
830        let bob_y = AckedTestDgm::from_welcome(bob_y, alice_y.clone()).unwrap();
831
832        // Bob acks their own add, Charlie doesn't ack yet.
833
834        let alice_y = AckedTestDgm::ack(
835            alice_y,
836            bob,
837            MessageId {
838                sender: alice,
839                seq: 1,
840            },
841        )
842        .unwrap();
843
844        // Both Alice and Bob consider all three members of the set.
845
846        assert_eq!(
847            AckedTestDgm::members_view(&alice_y, &alice).unwrap(),
848            HashSet::from([alice, bob, charlie])
849        );
850
851        assert_eq!(
852            AckedTestDgm::members_view(&alice_y, &bob).unwrap(),
853            HashSet::from([alice, bob, charlie])
854        );
855
856        assert_eq!(
857            AckedTestDgm::members_view(&bob_y, &alice).unwrap(),
858            HashSet::from([alice, bob, charlie])
859        );
860
861        assert_eq!(
862            AckedTestDgm::members_view(&bob_y, &bob).unwrap(),
863            HashSet::from([alice, bob, charlie])
864        );
865
866        // Charlie didn't ack added Bob yet.
867
868        assert_eq!(
869            AckedTestDgm::members_view(&alice_y, &charlie).unwrap(),
870            HashSet::from([alice, charlie])
871        );
872
873        assert_eq!(
874            AckedTestDgm::members_view(&bob_y, &charlie).unwrap(),
875            HashSet::from([alice, charlie])
876        );
877
878        assert_eq!(
879            AckedTestDgm::members_view(&charlie_y, &charlie).unwrap(),
880            HashSet::from([alice, charlie])
881        );
882
883        // Charlie processes and acks added Bob.
884
885        let charlie_y = AckedTestDgm::add(
886            charlie_y,
887            alice,
888            bob,
889            MessageId {
890                sender: alice,
891                seq: 1,
892            },
893        )
894        .unwrap();
895
896        let alice_y = AckedTestDgm::ack(
897            alice_y,
898            charlie,
899            MessageId {
900                sender: alice,
901                seq: 1,
902            },
903        )
904        .unwrap();
905
906        let bob_y = AckedTestDgm::ack(
907            bob_y,
908            charlie,
909            MessageId {
910                sender: alice,
911                seq: 1,
912            },
913        )
914        .unwrap();
915
916        // Everyone should have the same view.
917
918        for id in [alice, bob, charlie] {
919            assert_eq!(
920                AckedTestDgm::members_view(&alice_y, &id).unwrap(),
921                HashSet::from([alice, bob, charlie])
922            );
923
924            assert_eq!(
925                AckedTestDgm::members_view(&bob_y, &id).unwrap(),
926                HashSet::from([alice, bob, charlie])
927            );
928
929            assert_eq!(
930                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
931                HashSet::from([alice, bob, charlie])
932            );
933        }
934
935        // Charlie removes Bob.
936
937        let charlie_y = AckedTestDgm::remove(
938            charlie_y,
939            charlie,
940            &bob,
941            MessageId {
942                sender: charlie,
943                seq: 2,
944            },
945        )
946        .unwrap();
947
948        // Alice and Bob process the removal.
949
950        let alice_y = AckedTestDgm::remove(
951            alice_y,
952            charlie,
953            &bob,
954            MessageId {
955                sender: charlie,
956                seq: 2,
957            },
958        )
959        .unwrap();
960
961        let bob_y = AckedTestDgm::remove(
962            bob_y,
963            charlie,
964            &bob,
965            MessageId {
966                sender: charlie,
967                seq: 2,
968            },
969        )
970        .unwrap();
971
972        // Everyone considers for themselves and for Charlie (the "remover") that Bob is removed
973        // from the group.
974
975        assert_eq!(
976            AckedTestDgm::members_view(&charlie_y, &charlie).unwrap(),
977            HashSet::from([alice, charlie])
978        );
979
980        for id in [alice, charlie] {
981            assert_eq!(
982                AckedTestDgm::members_view(&alice_y, &id).unwrap(),
983                HashSet::from([alice, charlie])
984            );
985        }
986
987        for id in [bob, charlie] {
988            assert_eq!(
989                AckedTestDgm::members_view(&bob_y, &id).unwrap(),
990                HashSet::from([alice, charlie])
991            );
992        }
993
994        // .. but they assume so far that the other's still consider Bob part of the group (because
995        // no acks have been observed yet).
996
997        assert_eq!(
998            AckedTestDgm::members_view(&alice_y, &bob).unwrap(),
999            HashSet::from([alice, bob, charlie]),
1000        );
1001
1002        assert_eq!(
1003            AckedTestDgm::members_view(&bob_y, &alice).unwrap(),
1004            HashSet::from([alice, bob, charlie]),
1005        );
1006
1007        for id in [alice, bob] {
1008            assert_eq!(
1009                AckedTestDgm::members_view(&charlie_y, &id).unwrap(),
1010                HashSet::from([alice, bob, charlie]),
1011                "invalid members view from 2's perspective for {id}",
1012            );
1013        }
1014    }
1015
1016    #[test]
1017    fn strong_removal() {
1018        let alice = 0;
1019        let bob = 1;
1020        let charlie = 2;
1021        let daphne = 3;
1022
1023        // Alice creates a group.
1024
1025        let alice_y = AckedTestDgm::create(alice, &[alice]).unwrap();
1026
1027        // Alice adds Bob.
1028
1029        let alice_y = AckedTestDgm::add(
1030            alice_y,
1031            alice,
1032            bob,
1033            MessageId {
1034                sender: alice,
1035                seq: 0,
1036            },
1037        )
1038        .unwrap();
1039
1040        let bob_y = AckedTestDgm::init(bob);
1041        let bob_y = AckedTestDgm::from_welcome(bob_y, alice_y.clone()).unwrap();
1042
1043        // Alice removes Bob.
1044
1045        let alice_y = AckedTestDgm::remove(
1046            alice_y,
1047            alice,
1048            &bob,
1049            MessageId {
1050                sender: alice,
1051                seq: 0,
1052            },
1053        )
1054        .unwrap();
1055
1056        // Concurrently Bob adds Charlie and Daphne.
1057
1058        let bob_y = AckedTestDgm::add(
1059            bob_y,
1060            bob,
1061            charlie,
1062            MessageId {
1063                sender: bob,
1064                seq: 0,
1065            },
1066        )
1067        .unwrap();
1068
1069        let bob_y = AckedTestDgm::add(
1070            bob_y,
1071            bob,
1072            daphne,
1073            MessageId {
1074                sender: bob,
1075                seq: 1,
1076            },
1077        )
1078        .unwrap();
1079
1080        // Alice applies Bob's changes.
1081
1082        let alice_y = AckedTestDgm::add(
1083            alice_y,
1084            bob,
1085            charlie,
1086            MessageId {
1087                sender: bob,
1088                seq: 0,
1089            },
1090        )
1091        .unwrap();
1092
1093        let alice_y = AckedTestDgm::add(
1094            alice_y,
1095            bob,
1096            daphne,
1097            MessageId {
1098                sender: bob,
1099                seq: 1,
1100            },
1101        )
1102        .unwrap();
1103
1104        // Bob applies Alice's changes.
1105
1106        let bob_y = AckedTestDgm::remove(
1107            bob_y,
1108            alice,
1109            &bob,
1110            MessageId {
1111                sender: alice,
1112                seq: 1,
1113            },
1114        )
1115        .unwrap();
1116
1117        assert_eq!(
1118            AckedTestDgm::members_view(&alice_y, &alice).unwrap(),
1119            AckedTestDgm::members_view(&bob_y, &bob).unwrap(),
1120        );
1121        assert!(
1122            !AckedTestDgm::members_view(&alice_y, &alice)
1123                .unwrap()
1124                .contains(&bob)
1125        );
1126        assert!(
1127            !AckedTestDgm::members_view(&alice_y, &alice)
1128                .unwrap()
1129                .contains(&charlie)
1130        );
1131        assert!(
1132            !AckedTestDgm::members_view(&alice_y, &alice)
1133                .unwrap()
1134                .contains(&daphne)
1135        );
1136    }
1137}