Correctly abridge events after changes in joined users

This commit is contained in:
Andrea Maria Piana 2022-09-13 17:07:34 +01:00
parent 4d93ea83be
commit 9b670ff453
3 changed files with 182 additions and 151 deletions

View File

@ -481,14 +481,14 @@ func (s *MessageSender) EncodeMembershipUpdate(
} }
// EncodeAbridgedMembershipUpdate takes a group and an optional chat message and returns the protobuf representation to be sent on the wire. // EncodeAbridgedMembershipUpdate takes a group and an optional chat message and returns the protobuf representation to be sent on the wire.
// Only the events relevant to the sender are encoded // Only the events relevant to the current group are encoded
func (s *MessageSender) EncodeAbridgedMembershipUpdate( func (s *MessageSender) EncodeAbridgedMembershipUpdate(
group *v1protocol.Group, group *v1protocol.Group,
chatEntity ChatEntity, chatEntity ChatEntity,
) ([]byte, error) { ) ([]byte, error) {
message := v1protocol.MembershipUpdateMessage{ message := v1protocol.MembershipUpdateMessage{
ChatID: group.ChatID(), ChatID: group.ChatID(),
Events: group.AbridgedEvents(&s.identity.PublicKey), Events: group.AbridgedEvents(),
} }
return s.encodeMembershipUpdate(message, chatEntity) return s.encodeMembershipUpdate(message, chatEntity)
} }

View File

@ -356,15 +356,23 @@ func isInSlice(m string, set []string) bool {
} }
// AbridgedEvents returns the minimum set of events for a user to publish a post // AbridgedEvents returns the minimum set of events for a user to publish a post
func (g Group) AbridgedEvents(publicKey *ecdsa.PublicKey) []MembershipUpdateEvent { // The events we want to keep:
// 1) Chat created
// 2) Latest color changed
// 3) Latest image changed
// 4) For each admin, the latest admins added event that contains them
// 5) For each member, the latest members added event that contains them
// 4 & 5, might bring removed admins or removed members, for those, we also need to
// keep the event that removes them
func (g Group) AbridgedEvents() []MembershipUpdateEvent {
var events []MembershipUpdateEvent var events []MembershipUpdateEvent
var nameChangedEventFound bool var nameChangedEventFound bool
var colorChangedEventFound bool var colorChangedEventFound bool
var imageChangedEventFound bool var imageChangedEventFound bool
var joinedEventFound bool removedMembers := make(map[string]*MembershipUpdateEvent)
memberID := publicKeyToString(publicKey) addedMembers := make(map[string]bool)
var addedEventFound bool extraMembers := make(map[string]bool)
nextInChain := memberID admins := make(map[string]bool)
// Iterate in reverse // Iterate in reverse
for i := len(g.events) - 1; i >= 0; i-- { for i := len(g.events) - 1; i >= 0; i-- {
event := g.events[i] event := g.events[i]
@ -389,45 +397,60 @@ func (g Group) AbridgedEvents(publicKey *ecdsa.PublicKey) []MembershipUpdateEven
} }
events = append(events, event) events = append(events, event)
imageChangedEventFound = true imageChangedEventFound = true
case protobuf.MembershipUpdateEvent_MEMBERS_ADDED: case protobuf.MembershipUpdateEvent_MEMBERS_ADDED:
// If we already have an added event var shouldAddEvent bool
// or the user is not in slice, ignore for _, m := range event.Members {
if addedEventFound { // If it's adding a current user, and we don't have a more
continue // recent event
// if it's an admin, we track it
if admins[m] || (g.members.Has(m) && !addedMembers[m]) {
addedMembers[m] = true
shouldAddEvent = true
}
}
if shouldAddEvent {
// Append the event and check the not current members that are also
// added
for _, m := range event.Members {
if !g.members.Has(m) && !admins[m] {
extraMembers[m] = true
}
} }
areWeTheTarget := isInSlice(nextInChain, event.Members)
// If it's us, and we have been added by the creator, no more work to do, this is authoritative
if areWeTheTarget && g.events[0].From == event.From {
addedEventFound = true
events = append(events, event)
} else if areWeTheTarget {
// if it's us and we haven't been added by the creator, we follow the history of whoever invited us
nextInChain = event.From
events = append(events, event) events = append(events, event)
} }
case protobuf.MembershipUpdateEvent_MEMBER_JOINED: case protobuf.MembershipUpdateEvent_ADMIN_REMOVED:
if joinedEventFound || event.From != memberID { // We add it always for now
continue
}
joinedEventFound = true
events = append(events, event) events = append(events, event)
case protobuf.MembershipUpdateEvent_ADMINS_ADDED: case protobuf.MembershipUpdateEvent_ADMINS_ADDED:
if isInSlice(nextInChain, event.Members) { // We track admins in full
admins[event.Members[0]] = true
events = append(events, event)
case protobuf.MembershipUpdateEvent_MEMBER_REMOVED:
// Save member removed events, as we might need it
// to remove members who have been added but subsequently left
if removedMembers[event.Members[0]] == nil || removedMembers[event.Members[0]].ClockValue < event.ClockValue {
removedMembers[event.Members[0]] = &event
}
case protobuf.MembershipUpdateEvent_MEMBER_JOINED:
if g.members.Has(event.From) {
events = append(events, event) events = append(events, event)
} }
}
} }
// Reverse events
for i, j := 0, len(events)-1; i < j; i, j = i+1, j-1 {
events[i], events[j] = events[j], events[i]
} }
for m := range extraMembers {
if removedMembers[m] != nil {
events = append(events, *removedMembers[m])
}
}
sort.Slice(events, func(i, j int) bool {
return events[i].ClockValue < events[j].ClockValue
})
return events return events
} }

View File

@ -371,7 +371,45 @@ func TestMembershipUpdateEventEqual(t *testing.T) {
require.False(t, u1.Equal(u2)) require.False(t, u1.Equal(u2))
} }
func TestAbridgedEvents(t *testing.T) { func TestAbridgedEventsNameChanged(t *testing.T) {
var clock uint64 = 0
creator, err := crypto.GenerateKey()
require.NoError(t, err)
creatorID := publicKeyToString(&creator.PublicKey)
g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
require.NoError(t, err)
clock++
// Full events is only a single one
require.Len(t, g.Events(), 1)
// same as abridged
require.Len(t, g.AbridgedEvents(), 1)
// We change name of the chat
nameChangedEvent1 := NewNameChangedEvent("name-1", clock)
nameChangedEvent1.From = creatorID
nameChangedEvent1.ChatID = g.chatID
err = g.ProcessEvent(nameChangedEvent1)
require.NoError(t, err)
clock++
// We change name of the chat again
nameChangedEvent2 := NewNameChangedEvent("name-2", clock)
nameChangedEvent2.From = creatorID
nameChangedEvent2.ChatID = g.chatID
err = g.ProcessEvent(nameChangedEvent2)
require.NoError(t, err)
clock++
// Full events is 3 events
require.Len(t, g.Events(), 3)
// While abridged should exclude the first name-1 event
require.Len(t, g.AbridgedEvents(), 2)
require.Equal(t, g.AbridgedEvents()[1].Name, "name-2")
}
func TestAbridgedEventsMembers(t *testing.T) {
var clock uint64 = 0 var clock uint64 = 0
creator, err := crypto.GenerateKey() creator, err := crypto.GenerateKey()
require.NoError(t, err) require.NoError(t, err)
@ -400,141 +438,111 @@ func TestAbridgedEvents(t *testing.T) {
// Full events is only a single one // Full events is only a single one
require.Len(t, g.Events(), 1) require.Len(t, g.Events(), 1)
// same as abridged // same as abridged
require.Len(t, g.AbridgedEvents(&creator.PublicKey), 1) require.Len(t, g.AbridgedEvents(), 1)
// We change name of the chat // Add three new members
nameChangedEvent1 := NewNameChangedEvent("name-1", clock) event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
nameChangedEvent1.From = creatorID event.From = creatorID
nameChangedEvent1.ChatID = g.chatID event.ChatID = g.chatID
err = g.ProcessEvent(nameChangedEvent1) err = g.ProcessEvent(event)
require.NoError(t, err) require.NoError(t, err)
clock++ clock++
// We change name of the chat again require.Len(t, g.Events(), 2)
nameChangedEvent2 := NewNameChangedEvent("name-2", clock) // All the events are relevant here, so it should be the same
nameChangedEvent2.From = creatorID require.Len(t, g.AbridgedEvents(), 2)
nameChangedEvent2.ChatID = g.chatID
err = g.ProcessEvent(nameChangedEvent2) // We remove one of the users
event = NewMemberRemovedEvent(member3ID, clock)
event.From = creatorID
event.ChatID = g.chatID
err = g.ProcessEvent(event)
require.NoError(t, err) require.NoError(t, err)
clock++ clock++
// Full events is 3 events
require.Len(t, g.Events(), 3) require.Len(t, g.Events(), 3)
// While abridged should exclude the first name-1 event // All the events are relevant here, so it should be the same
require.Len(t, g.AbridgedEvents(&creator.PublicKey), 2) require.Len(t, g.AbridgedEvents(), 3)
require.Equal(t, g.AbridgedEvents(&creator.PublicKey)[1].Name, "name-2")
// Add a new member // Add a new member
newMemberEvent1 := NewMembersAddedEvent([]string{member1ID}, clock) event = NewMembersAddedEvent([]string{member4ID}, clock)
newMemberEvent1.From = creatorID event.From = creatorID
newMemberEvent1.ChatID = g.chatID event.ChatID = g.chatID
err = g.ProcessEvent(newMemberEvent1) err = g.ProcessEvent(event)
require.NoError(t, err) require.NoError(t, err)
clock++ clock++
// Full events is 4 events
require.Len(t, g.Events(), 4) require.Len(t, g.Events(), 4)
// While abridged, given we are the creator, we only take 2 events and ignore // All the events are relevant here, so it should be the same
// the member created event require.Len(t, g.AbridgedEvents(), 4)
require.Len(t, g.AbridgedEvents(&creator.PublicKey), 2)
require.Equal(t, g.AbridgedEvents(&creator.PublicKey)[1].Name, "name-2")
// While abridged, given we are the new member, we take 3 events // We remove the member just added
// that are relevant to us event = NewMemberRemovedEvent(member4ID, clock)
require.Len(t, g.AbridgedEvents(&member1.PublicKey), 3) event.From = creatorID
require.Equal(t, g.AbridgedEvents(&member1.PublicKey)[1].Name, "name-2") event.ChatID = g.chatID
require.Equal(t, g.AbridgedEvents(&member1.PublicKey)[2].Members, []string{member1ID}) err = g.ProcessEvent(event)
// We join the chat
joinedEvent1 := NewMemberJoinedEvent(clock)
joinedEvent1.From = member1ID
joinedEvent1.ChatID = g.chatID
err = g.ProcessEvent(joinedEvent1)
require.NoError(t, err) require.NoError(t, err)
clock++ clock++
// Full events is 5 events
require.Len(t, g.Events(), 5) require.Len(t, g.Events(), 5)
// While abridged, given we are the creator, we only take 2 events and ignore // The previous two events, should be removed, because they have no impact
// the member created event // on the chat history
require.Len(t, g.AbridgedEvents(&creator.PublicKey), 2) abridgedEvents := g.AbridgedEvents()
require.Equal(t, g.AbridgedEvents(&creator.PublicKey)[1].Name, "name-2") require.Len(t, abridgedEvents, 3)
// While abridged, given we are the new member, we take 4 events require.Equal(t, uint64(0), abridgedEvents[0].ClockValue)
// that are relevant to us require.Equal(t, uint64(1), abridgedEvents[1].ClockValue)
require.Len(t, g.AbridgedEvents(&member1.PublicKey), 4) require.Equal(t, uint64(2), abridgedEvents[2].ClockValue)
}
// Next is the tricky case, a user that has been invited by someone
// made an admin. We need to follow the history of admins so func TestAbridgedEventsAdmins(t *testing.T) {
// that whoever receives the message can see that Creator-> Invited A -> Made A admin -> A Invited B var clock uint64 = 0
creator, err := crypto.GenerateKey()
// Creator makes member1 Admin require.NoError(t, err)
addedAdminEvent1 := NewAdminsAddedEvent([]string{member1ID}, clock) creatorID := publicKeyToString(&creator.PublicKey)
addedAdminEvent1.From = creatorID
addedAdminEvent1.ChatID = g.chatID member1, err := crypto.GenerateKey()
err = g.ProcessEvent(addedAdminEvent1) require.NoError(t, err)
require.NoError(t, err) member1ID := publicKeyToString(&member1.PublicKey)
clock++
member2, err := crypto.GenerateKey()
// member1 adds member2 require.NoError(t, err)
newMemberEvent2 := NewMembersAddedEvent([]string{member2ID}, clock) member2ID := publicKeyToString(&member2.PublicKey)
newMemberEvent2.From = member1ID
newMemberEvent2.ChatID = g.chatID member3, err := crypto.GenerateKey()
err = g.ProcessEvent(newMemberEvent2) require.NoError(t, err)
require.NoError(t, err) member3ID := publicKeyToString(&member3.PublicKey)
clock++
g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
// member1 makes member2 admin require.NoError(t, err)
addedAdminEvent2 := NewAdminsAddedEvent([]string{member2ID}, clock) clock++
addedAdminEvent2.From = member1ID
addedAdminEvent2.ChatID = g.chatID // Full events is only a single one
err = g.ProcessEvent(addedAdminEvent2) require.Len(t, g.Events(), 1)
require.NoError(t, err) // same as abridged
clock++ require.Len(t, g.AbridgedEvents(), 1)
// member2 adds member3 // Add three new members
newMemberEvent3 := NewMembersAddedEvent([]string{member3ID}, clock) event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
newMemberEvent3.From = member2ID event.From = creatorID
newMemberEvent3.ChatID = g.chatID event.ChatID = g.chatID
err = g.ProcessEvent(newMemberEvent3) err = g.ProcessEvent(event)
require.NoError(t, err) require.NoError(t, err)
clock++ clock++
// member1 makes member3 admin require.Len(t, g.Events(), 2)
addedAdminEvent3 := NewAdminsAddedEvent([]string{member3ID}, clock) // All the events are relevant here, so it should be the same
addedAdminEvent3.From = member1ID require.Len(t, g.AbridgedEvents(), 2)
addedAdminEvent3.ChatID = g.chatID
err = g.ProcessEvent(addedAdminEvent3) // Make two of them admins
require.NoError(t, err) event = NewAdminsAddedEvent([]string{member1ID, member2ID}, clock)
clock++ event.From = creatorID
event.ChatID = g.chatID
// member3 adds member4 err = g.ProcessEvent(event)
newMemberEvent4 := NewMembersAddedEvent([]string{member4ID}, clock) require.NoError(t, err)
newMemberEvent4.From = member3ID clock++
newMemberEvent4.ChatID = g.chatID
err = g.ProcessEvent(newMemberEvent4) require.Len(t, g.Events(), 3)
require.NoError(t, err) // All the events are relevant here, so it should be the same
require.Len(t, g.AbridgedEvents(), 3)
// Now we check that the history has been correctly followed
// Full events is 4 events
require.Len(t, g.Events(), 11)
// While abridged, given we are the creator, we only take 2 events and ignore
// the member created event
require.Len(t, g.AbridgedEvents(&creator.PublicKey), 2)
require.Equal(t, g.AbridgedEvents(&creator.PublicKey)[1].Name, "name-2")
// While abridged, given we are the new member, we take 3 events
// that are relevant to us
require.Len(t, g.AbridgedEvents(&member4.PublicKey), 9)
// We build a group from the abridged events
group, err := NewGroupWithEvents(g.chatID, g.AbridgedEvents(&member4.PublicKey))
require.NoError(t, err)
// Make sure the chatID, name is the same
require.Equal(t, g.name, group.name)
require.Equal(t, g.chatID, group.chatID)
// Make sure that user 4 is a member
require.True(t, group.IsMember(member4ID))
} }