Correctly abridge events after changes in joined users
This commit is contained in:
parent
4d93ea83be
commit
9b670ff453
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue