From 77541725aac06d1516e5cc553bf19521b7b0ec2b Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov Date: Fri, 17 May 2024 18:15:39 +0200 Subject: [PATCH] chore(community)_: reevaluateMembers optimization (#5169) * chore(community)_: reevaluateMembers optinizations --- protocol/communities/community.go | 9 + protocol/communities/manager.go | 278 ++++++++++++------ protocol/communities/manager_test.go | 40 ++- protocol/communities/permission_checker.go | 114 +++++-- protocol/communities/persistence.go | 56 ++++ protocol/communities/persistence_test.go | 52 ++++ protocol/communities/utils.go | 2 +- protocol/communities_messenger_test.go | 3 +- ...nities_messenger_token_permissions_test.go | 121 ++++++++ 9 files changed, 542 insertions(+), 133 deletions(-) diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 78d2e41d8..d14e7f039 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -2186,6 +2186,14 @@ func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey, return changes, nil } +func (o *Community) PopulateChannelsWithAllMembers() { + members := o.Members() + for _, channel := range o.Chats() { + channel.Members = members + } + o.increaseClock() +} + func (o *Community) PopulateChatWithAllMembers(chatID string) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() @@ -2479,6 +2487,7 @@ func (o *Community) deleteTokenPermission(permissionID string) (*CommunityChange changes := o.emptyCommunityChanges() changes.TokenPermissionsRemoved[permissionID] = NewCommunityTokenPermission(permission) + return changes, nil } diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 4fe2f7521..b9425ca89 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -991,6 +991,11 @@ func (m *Manager) EditCommunityTokenPermission(request *requests.EditCommunityTo return community, changes, nil } +// use it only for testing purposes +func (m *Manager) ReevaluateMembers(communityID types.HexBytes) (*Community, map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey, error) { + return m.reevaluateMembers(communityID) +} + func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey, error) { m.communityLock.Lock(communityID) defer m.communityLock.Unlock(communityID) @@ -1006,16 +1011,23 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map return nil, nil, ErrNotEnoughPermissions } - becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER) - becomeAdminPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_ADMIN) - becomeTokenMasterPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER) + communityPermissionsPreParsedData, channelPermissionsPreParsedData := PreParsePermissionsData(community.tokenPermissions()) - hasMemberPermissions := len(becomeMemberPermissions) > 0 + hasMemberPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER] != nil + + if len(channelPermissionsPreParsedData) == 0 { + community.PopulateChannelsWithAllMembers() + } newPrivilegedRoles := make(map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) newPrivilegedRoles[protobuf.CommunityMember_ROLE_TOKEN_MASTER] = []*ecdsa.PublicKey{} newPrivilegedRoles[protobuf.CommunityMember_ROLE_ADMIN] = []*ecdsa.PublicKey{} + membersAccounts, err := m.persistence.GetCommunityRequestsToJoinRevealedAddresses(community.ID()) + if err != nil { + return nil, nil, err + } + for memberKey := range community.Members() { memberPubKey, err := common.HexToPubkey(memberKey) if err != nil { @@ -1028,13 +1040,9 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map isCurrentRoleTokenMaster := community.IsMemberTokenMaster(memberPubKey) isCurrentRoleAdmin := community.IsMemberAdmin(memberPubKey) - requestID := CalculateRequestID(memberKey, community.ID()) - revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(requestID) - if err != nil { - return nil, nil, err - } - memberHasWallet := len(revealedAccounts) > 0 + revealedAccount, exists := membersAccounts[memberKey] + memberHasWallet := exists // Check if user has privilege role without sharing the account to controlNode // or user treated as a member without wallet in closed community @@ -1046,9 +1054,13 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map continue } - accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(revealedAccounts) + accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(revealedAccount) - isNewRoleTokenMaster, err := m.ReevaluatePrivilegedMember(community, becomeTokenMasterPermissions, accountsAndChainIDs, memberPubKey, + isNewRoleTokenMaster, err := m.ReevaluatePrivilegedMember( + community, + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER], + accountsAndChainIDs, + memberPubKey, protobuf.CommunityMember_ROLE_TOKEN_MASTER, isCurrentRoleTokenMaster) if err != nil { @@ -1064,7 +1076,11 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map continue } - isNewRoleAdmin, err := m.ReevaluatePrivilegedMember(community, becomeAdminPermissions, accountsAndChainIDs, memberPubKey, + isNewRoleAdmin, err := m.ReevaluatePrivilegedMember( + community, + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN], + accountsAndChainIDs, + memberPubKey, protobuf.CommunityMember_ROLE_ADMIN, isCurrentRoleAdmin) if err != nil { @@ -1081,7 +1097,10 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map } if hasMemberPermissions { - permissionResponse, err := m.PermissionChecker.CheckPermissions(becomeMemberPermissions, accountsAndChainIDs, true) + permissionResponse, err := m.PermissionChecker.CheckPermissions( + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER], + accountsAndChainIDs, + true) if err != nil { return nil, nil, err } @@ -1096,52 +1115,96 @@ func (m *Manager) reevaluateMembers(communityID types.HexBytes) (*Community, map } } - // Validate channel permissions - for channelID := range community.Chats() { - chatID := community.ChatID(channelID) - - viewOnlyPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) - viewAndPostPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) - - if len(viewOnlyPermissions) == 0 && len(viewAndPostPermissions) == 0 { - // ensure all members are added back if channel permissions were removed - _, err = community.PopulateChatWithAllMembers(channelID) - if err != nil { - return nil, nil, err - } - continue - } - - response, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountsAndChainIDs, true) - if err != nil { - return nil, nil, err - } - - isMemberAlreadyInChannel := community.IsMemberInChat(memberPubKey, channelID) - - if response.ViewOnlyPermissions.Satisfied || response.ViewAndPostPermissions.Satisfied { - channelRole := protobuf.CommunityMember_CHANNEL_ROLE_VIEWER - if response.ViewAndPostPermissions.Satisfied { - channelRole = protobuf.CommunityMember_CHANNEL_ROLE_POSTER - } - - // Add the member back to the chat member list in case the role changed (it replaces the previous values) - _, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{}, channelRole) - if err != nil { - return nil, nil, err - } - } else if isMemberAlreadyInChannel { - _, err := community.RemoveUserFromChat(memberPubKey, channelID) - if err != nil { - return nil, nil, err - } - } + err = m.reevaluateMemberChannelsPermissions(community, memberPubKey, channelPermissionsPreParsedData, accountsAndChainIDs) + if err != nil { + return nil, nil, err } } return community, newPrivilegedRoles, m.saveAndPublish(community) } +func (m *Manager) reevaluateMemberChannelsPermissions(community *Community, memberPubKey *ecdsa.PublicKey, + channelPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination) error { + + if len(channelPermissionsPreParsedData) == 0 { + return nil + } + + // check which permissions we satisfy and which not + channelPermissionsCheckResult, err := m.checkChannelsPermissions(channelPermissionsPreParsedData, accountsAndChainIDs, true) + if err != nil { + return err + } + + for channelID := range community.Chats() { + chatID := community.ChatID(channelID) + isMemberAlreadyInChannel := community.IsMemberInChat(memberPubKey, channelID) + + channelPermissionsCheckResult, exists := channelPermissionsCheckResult[chatID] + + // if channel permissions were removed member must be added back + if !exists { + if !isMemberAlreadyInChannel { + _, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER) + if err != nil { + return err + } + } + continue + } + + viewAndPostSatisfied, viewAndPosPermissionExists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL] + viewOnlySatisfied, viewOnlyPermissionExists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL] + + satisfied := false + channelRole := protobuf.CommunityMember_CHANNEL_ROLE_VIEWER + if viewAndPosPermissionExists && viewAndPostSatisfied { + satisfied = viewAndPostSatisfied + channelRole = protobuf.CommunityMember_CHANNEL_ROLE_POSTER + } else if !satisfied && viewOnlyPermissionExists { + satisfied = viewOnlySatisfied + } + + if satisfied { + // Add the member back to the chat member list in case the role changed (it replaces the previous values) + _, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{}, channelRole) + if err != nil { + return err + } + } else if !satisfied && isMemberAlreadyInChannel { + _, err := community.RemoveUserFromChat(memberPubKey, channelID) + if err != nil { + return err + } + } + } + return nil +} + +func (m *Manager) checkChannelsPermissions(channelsPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (map[string]map[protobuf.CommunityTokenPermission_Type]bool, error) { + channelPermissionsCheckResult := make(map[string]map[protobuf.CommunityTokenPermission_Type]bool) + for _, channelsPermissionPreParsedData := range channelsPermissionsPreParsedData { + permissionResponse, err := m.PermissionChecker.CheckPermissions(channelsPermissionPreParsedData, accountsAndChainIDs, true) + if err != nil { + return channelPermissionsCheckResult, err + } + // Note: in `PreParsedCommunityPermissionsData` for channels there will be only one permission + // no need to iterate over `Permissions` + for _, chatId := range channelsPermissionPreParsedData.Permissions[0].ChatIds { + if _, exists := channelPermissionsCheckResult[chatId]; !exists { + channelPermissionsCheckResult[chatId] = make(map[protobuf.CommunityTokenPermission_Type]bool) + } + satisfied, exists := channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type] + if exists && satisfied { + continue + } + channelPermissionsCheckResult[chatId][channelsPermissionPreParsedData.Permissions[0].Type] = permissionResponse.Satisfied + } + } + return channelPermissionsCheckResult, nil +} + func (m *Manager) StartMembersReevaluationLoop(communityID types.HexBytes, reevaluateOnStart bool) { go m.reevaluateMembersLoop(communityID, reevaluateOnStart) } @@ -2462,24 +2525,22 @@ func (m *Manager) CheckPermissionToJoin(id []byte, addresses []gethcommon.Addres } return m.PermissionChecker.CheckPermissionToJoin(community, addresses) - } -func (m *Manager) accountsSatisfyPermissionsToJoin(community *Community, accounts []*protobuf.RevealedAccount) (bool, protobuf.CommunityMember_Roles, error) { - accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(accounts) - becomeAdminPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_ADMIN) - becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER) - becomeTokenMasterPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER) +func (m *Manager) accountsSatisfyPermissionsToJoin( + communityPermissionsPreParsedData map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData, + accountsAndChainIDs []*AccountChainIDsCombination) (bool, protobuf.CommunityMember_Roles, error) { - if m.accountsHasPrivilegedPermission(becomeTokenMasterPermissions, accountsAndChainIDs) { + if m.accountsHasPrivilegedPermission(communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER], accountsAndChainIDs) { return true, protobuf.CommunityMember_ROLE_TOKEN_MASTER, nil } - if m.accountsHasPrivilegedPermission(becomeAdminPermissions, accountsAndChainIDs) { + if m.accountsHasPrivilegedPermission(communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN], accountsAndChainIDs) { return true, protobuf.CommunityMember_ROLE_ADMIN, nil } - if len(becomeMemberPermissions) > 0 { - permissionResponse, err := m.PermissionChecker.CheckPermissions(becomeMemberPermissions, accountsAndChainIDs, true) + preParsedBecomeMemberPermissions := communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER] + if preParsedBecomeMemberPermissions != nil { + permissionResponse, err := m.PermissionChecker.CheckPermissions(preParsedBecomeMemberPermissions, accountsAndChainIDs, true) if err != nil { return false, protobuf.CommunityMember_ROLE_NONE, err } @@ -2490,37 +2551,49 @@ func (m *Manager) accountsSatisfyPermissionsToJoin(community *Community, account return true, protobuf.CommunityMember_ROLE_NONE, nil } -func (m *Manager) accountsSatisfyPermissionsToJoinChannels(community *Community, accounts []*protobuf.RevealedAccount) (map[string]*protobuf.CommunityChat, map[string]*protobuf.CommunityChat, error) { +func (m *Manager) accountsSatisfyPermissionsToJoinChannels( + community *Community, + channelPermissionsPreParsedData map[string]*PreParsedCommunityPermissionsData, + accountsAndChainIDs []*AccountChainIDsCombination) (map[string]*protobuf.CommunityChat, map[string]*protobuf.CommunityChat, error) { + viewChats := make(map[string]*protobuf.CommunityChat) viewAndPostChats := make(map[string]*protobuf.CommunityChat) - accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(accounts) + if len(channelPermissionsPreParsedData) == 0 { + for channelID, channel := range community.config.CommunityDescription.Chats { + viewAndPostChats[channelID] = channel + } + + return viewChats, viewAndPostChats, nil + } + + // check which permissions we satisfy and which not + channelPermissionsCheckResult, err := m.checkChannelsPermissions(channelPermissionsPreParsedData, accountsAndChainIDs, true) + if err != nil { + m.logger.Warn("check channel permission failed: %v", zap.Error(err)) + return viewChats, viewAndPostChats, err + } for channelID, channel := range community.config.CommunityDescription.Chats { - channelViewOnlyPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) - channelViewAndPostPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) - channelPermissions := append(channelViewOnlyPermissions, channelViewAndPostPermissions...) + chatID := community.ChatID(channelID) + channelPermissionsCheckResult, exists := channelPermissionsCheckResult[chatID] - if len(channelPermissions) == 0 { + if !exists { viewAndPostChats[channelID] = channel continue } - permissionResponse, err := m.PermissionChecker.CheckPermissions(channelPermissions, accountsAndChainIDs, true) - if err != nil { - return nil, nil, err + viewAndPostSatisfied, exists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL] + if exists && viewAndPostSatisfied { + delete(viewChats, channelID) + viewAndPostChats[channelID] = channel + continue } - if permissionResponse.Satisfied { - highestRole := calculateRolesAndHighestRole(permissionResponse.Permissions).HighestRole - if highestRole == nil { - return nil, nil, errors.New("failed to calculate highest role") - } - switch highestRole.Role { - case protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL: + viewOnlySatisfied, exists := channelPermissionsCheckResult[protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL] + if exists && viewOnlySatisfied { + if _, exists := viewAndPostChats[channelID]; !exists { viewChats[channelID] = channel - case protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL: - viewAndPostChats[channelID] = channel } } } @@ -2548,7 +2621,11 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err return nil, err } - permissionsSatisfied, role, err := m.accountsSatisfyPermissionsToJoin(community, revealedAccounts) + accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(revealedAccounts) + + communityPermissionsPreParsedData, channelPermissionsPreParsedData := PreParsePermissionsData(community.tokenPermissions()) + + permissionsSatisfied, role, err := m.accountsSatisfyPermissionsToJoin(communityPermissionsPreParsedData, accountsAndChainIDs) if err != nil { return nil, err } @@ -2567,7 +2644,7 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err return nil, err } - viewChannels, postChannels, err := m.accountsSatisfyPermissionsToJoinChannels(community, revealedAccounts) + viewChannels, postChannels, err := m.accountsSatisfyPermissionsToJoinChannels(community, channelPermissionsPreParsedData, accountsAndChainIDs) if err != nil { return nil, err } @@ -3001,6 +3078,8 @@ func (m *Manager) CheckChannelPermissions(communityID types.HexBytes, chatID str viewOnlyPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) viewAndPostPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) allChainIDs, err := m.tokenManager.GetAllChainIDs() if err != nil { @@ -3008,7 +3087,7 @@ func (m *Manager) CheckChannelPermissions(communityID types.HexBytes, chatID str } accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs) - response, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountsAndChainIDs, false) + response, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountsAndChainIDs, false) if err != nil { return nil, err } @@ -3035,7 +3114,7 @@ type CheckChannelViewAndPostPermissionsResult struct { Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"` } -func (m *Manager) checkChannelPermissions(viewOnlyPermissions []*CommunityTokenPermission, viewAndPostPermissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckChannelPermissionsResponse, error) { +func (m *Manager) checkChannelPermissions(viewOnlyPreParsedPermissions *PreParsedCommunityPermissionsData, viewAndPostPreParsedPermissions *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckChannelPermissionsResponse, error) { response := &CheckChannelPermissionsResponse{ ViewOnlyPermissions: &CheckChannelViewOnlyPermissionsResult{ @@ -3048,18 +3127,18 @@ func (m *Manager) checkChannelPermissions(viewOnlyPermissions []*CommunityTokenP }, } - viewOnlyPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewOnlyPermissions, accountsAndChainIDs, shortcircuit) + viewOnlyPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewOnlyPreParsedPermissions, accountsAndChainIDs, shortcircuit) if err != nil { return nil, err } - viewAndPostPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewAndPostPermissions, accountsAndChainIDs, shortcircuit) + viewAndPostPermissionsResponse, err := m.PermissionChecker.CheckPermissions(viewAndPostPreParsedPermissions, accountsAndChainIDs, shortcircuit) if err != nil { return nil, err } - hasViewOnlyPermissions := len(viewOnlyPermissions) > 0 - hasViewAndPostPermissions := len(viewAndPostPermissions) > 0 + hasViewOnlyPermissions := viewOnlyPreParsedPermissions != nil + hasViewAndPostPermissions := viewAndPostPreParsedPermissions != nil if (hasViewAndPostPermissions && !hasViewOnlyPermissions) || (hasViewOnlyPermissions && hasViewAndPostPermissions && viewAndPostPermissionsResponse.Satisfied) { response.ViewOnlyPermissions.Satisfied = viewAndPostPermissionsResponse.Satisfied @@ -3096,11 +3175,13 @@ func (m *Manager) CheckAllChannelsPermissions(communityID types.HexBytes, addres Channels: make(map[string]*CheckChannelPermissionsResponse), } + // TODO: optimize for channelID := range channels { viewOnlyPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) viewAndPostPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) - - checkChannelPermissionsResponse, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountsAndChainIDs, false) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) + checkChannelPermissionsResponse, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountsAndChainIDs, false) if err != nil { return nil, err } @@ -4992,9 +5073,9 @@ func revealedAccountsToAccountsAndChainIDsCombination(revealedAccounts []*protob return accountsAndChainIDs } -func (m *Manager) accountsHasPrivilegedPermission(privilegedPermissions []*CommunityTokenPermission, accounts []*AccountChainIDsCombination) bool { - if len(privilegedPermissions) > 0 { - permissionResponse, err := m.PermissionChecker.CheckPermissions(privilegedPermissions, accounts, true) +func (m *Manager) accountsHasPrivilegedPermission(preParsedCommunityPermissionData *PreParsedCommunityPermissionsData, accounts []*AccountChainIDsCombination) bool { + if preParsedCommunityPermissionData != nil { + permissionResponse, err := m.PermissionChecker.CheckPermissions(preParsedCommunityPermissionData, accounts, true) if err != nil { m.logger.Warn("check privileged permission failed: %v", zap.Error(err)) return false @@ -5047,16 +5128,17 @@ func (m *Manager) GetRevealedAddresses(communityID types.HexBytes, memberPk stri return response, err } -func (m *Manager) ReevaluatePrivilegedMember(community *Community, tokenPermissions []*CommunityTokenPermission, +func (m *Manager) ReevaluatePrivilegedMember(community *Community, permissionsData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, memberPubKey *ecdsa.PublicKey, privilegedRole protobuf.CommunityMember_Roles, alreadyHasPrivilegedRole bool) (bool, error) { - hasPrivilegedRolePermissions := len(tokenPermissions) > 0 + hasPrivilegedRolePermissions := permissionsData != nil removeCurrentRole := false if hasPrivilegedRolePermissions { - permissionResponse, err := m.PermissionChecker.CheckPermissions(tokenPermissions, accountsAndChainIDs, true) + permissionResponse, err := m.PermissionChecker.CheckPermissions(permissionsData, accountsAndChainIDs, true) if err != nil { + m.logger.Warn("check privileged permission failed: %v", zap.Error(err)) return alreadyHasPrivilegedRole, err } else if permissionResponse.Satisfied && !alreadyHasPrivilegedRole { _, err = community.AddRoleToMember(memberPubKey, privilegedRole) diff --git a/protocol/communities/manager_test.go b/protocol/communities/manager_test.go index ad483af02..18879381c 100644 --- a/protocol/communities/manager_test.go +++ b/protocol/communities/manager_test.go @@ -210,6 +210,8 @@ func (s *ManagerSuite) TestRetrieveTokens() { }, } + preParsedPermissions := preParsedCommunityPermissionsData(permissions) + accountChainIDsCombination := []*AccountChainIDsCombination{ &AccountChainIDsCombination{ Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), @@ -218,14 +220,14 @@ func (s *ManagerSuite) TestRetrieveTokens() { } // Set response to exactly the right one tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) - resp, err := m.PermissionChecker.CheckPermissions(permissions, accountChainIDsCombination, false) + resp, err := m.PermissionChecker.CheckPermissions(preParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) s.Require().True(resp.Satisfied) // Set response to 0 tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) - resp, err = m.PermissionChecker.CheckPermissions(permissions, accountChainIDsCombination, false) + resp, err = m.PermissionChecker.CheckPermissions(preParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) s.Require().False(resp.Satisfied) @@ -259,6 +261,8 @@ func (s *ManagerSuite) TestRetrieveCollectibles() { }, } + preParsedPermissions := preParsedCommunityPermissionsData(permissions) + accountChainIDsCombination := []*AccountChainIDsCombination{ &AccountChainIDsCombination{ Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), @@ -269,7 +273,7 @@ func (s *ManagerSuite) TestRetrieveCollectibles() { // Set response to exactly the right one tokenBalances = []thirdparty.TokenBalance{tokenBalance(tokenID, 1)} cm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), tokenBalances) - resp, err := m.PermissionChecker.CheckPermissions(permissions, accountChainIDsCombination, false) + resp, err := m.PermissionChecker.CheckPermissions(preParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) s.Require().True(resp.Satisfied) @@ -277,7 +281,7 @@ func (s *ManagerSuite) TestRetrieveCollectibles() { // Set balances to 0 tokenBalances = []thirdparty.TokenBalance{} cm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), tokenBalances) - resp, err = m.PermissionChecker.CheckPermissions(permissions, accountChainIDsCombination, false) + resp, err = m.PermissionChecker.CheckPermissions(preParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) s.Require().False(resp.Satisfied) @@ -932,9 +936,11 @@ func (s *ManagerSuite) TestCheckChannelPermissions_NoPermissions() { var viewOnlyPermissions = make([]*CommunityTokenPermission, 0) var viewAndPostPermissions = make([]*CommunityTokenPermission, 0) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) - resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + resp, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -984,8 +990,11 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewOnlyPermissions() { var viewAndPostPermissions = make([]*CommunityTokenPermission, 0) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) - resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + resp, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -996,7 +1005,7 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewOnlyPermissions() { // Set response to exactly the right one tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) - resp, err = m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + resp, err = m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -1044,8 +1053,11 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissions() { var viewOnlyPermissions = make([]*CommunityTokenPermission, 0) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) - resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + resp, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -1056,7 +1068,7 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissions() { // Set response to exactly the right one tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) - resp, err = m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + resp, err = m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -1134,7 +1146,10 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissionsCombina // Set resopnse for viewAndPost permissions tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(testContractAddresses[chainID]), 0) - resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) + + resp, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) @@ -1213,7 +1228,10 @@ func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissionsCombina // Set resopnse for viewAndPost permissions tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(testContractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) - resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + viewOnlyPreParsedPermissions := preParsedCommunityPermissionsData(viewOnlyPermissions) + viewAndPostPreParsedPermissions := preParsedCommunityPermissionsData(viewAndPostPermissions) + + resp, err := m.checkChannelPermissions(viewOnlyPreParsedPermissions, viewAndPostPreParsedPermissions, accountChainIDsCombination, false) s.Require().NoError(err) s.Require().NotNil(resp) diff --git a/protocol/communities/permission_checker.go b/protocol/communities/permission_checker.go index 246892e9f..241a87306 100644 --- a/protocol/communities/permission_checker.go +++ b/protocol/communities/permission_checker.go @@ -22,7 +22,7 @@ import ( type PermissionChecker interface { CheckPermissionToJoin(*Community, []gethcommon.Address) (*CheckPermissionToJoinResponse, error) - CheckPermissions(permissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) + CheckPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) } type DefaultPermissionChecker struct { @@ -33,6 +33,18 @@ type DefaultPermissionChecker struct { logger *zap.Logger } +type PreParsedPermissionsData struct { + Erc721TokenRequirements map[uint64]map[string]*protobuf.TokenCriteria + Erc20TokenAddresses []gethcommon.Address + Erc20ChainIDsMap map[uint64]bool + Erc721ChainIDsMap map[uint64]bool +} + +type PreParsedCommunityPermissionsData struct { + *PreParsedPermissionsData + Permissions []*CommunityTokenPermission +} + func (p *DefaultPermissionChecker) getOwnedENS(addresses []gethcommon.Address) ([]string, error) { ownedENS := make([]string, 0) if p.ensVerifier == nil { @@ -159,8 +171,8 @@ func (p *DefaultPermissionChecker) CheckPermissionToJoin(community *Community, a return becomeMemberPermissionsResponse, nil } // If there are any admin or token master permissions, combine result. - - adminOrTokenPermissionsResponse, err := p.CheckPermissions(adminOrTokenMasterPermissionsToJoin, accountsAndChainIDs, false) + preParsedPermissions := preParsedCommunityPermissionsData(adminOrTokenMasterPermissionsToJoin) + adminOrTokenPermissionsResponse, err := p.CheckPermissions(preParsedPermissions, accountsAndChainIDs, false) if err != nil { return nil, err } @@ -191,13 +203,15 @@ func (p *DefaultPermissionChecker) checkPermissionsOrDefault(permissions []*Comm } return response, nil } - return p.CheckPermissions(permissions, accountsAndChainIDs, false) + + preParsedPermissions := preParsedCommunityPermissionsData(permissions) + return p.CheckPermissions(preParsedPermissions, accountsAndChainIDs, false) } // CheckPermissions will retrieve balances and check whether the user has // permission to join the community, if shortcircuit is true, it will stop as soon // as we know the answer -func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) { +func (p *DefaultPermissionChecker) CheckPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) { response := &CheckPermissionsResponse{ Satisfied: false, @@ -205,30 +219,25 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke ValidCombinations: make([]*AccountChainIDsCombination, 0), } - erc20TokenRequirements, erc721TokenRequirements, _ := ExtractTokenCriteria(permissions) + if permissionsParsedData == nil { + response.Satisfied = true + return response, nil + } - erc20ChainIDsMap := make(map[uint64]bool) - erc721ChainIDsMap := make(map[uint64]bool) + erc721TokenRequirements := permissionsParsedData.Erc721TokenRequirements + + erc20ChainIDsMap := permissionsParsedData.Erc20ChainIDsMap + erc721ChainIDsMap := permissionsParsedData.Erc721ChainIDsMap + + erc20TokenAddresses := permissionsParsedData.Erc20TokenAddresses - erc20TokenAddresses := make([]gethcommon.Address, 0) accounts := make([]gethcommon.Address, 0) + // TODO: move outside in order not to convert it for _, accountAndChainIDs := range accountsAndChainIDs { accounts = append(accounts, accountAndChainIDs.Address) } - // figure out chain IDs we're interested in - for chainID, tokens := range erc20TokenRequirements { - erc20ChainIDsMap[chainID] = true - for contractAddress := range tokens { - erc20TokenAddresses = append(erc20TokenAddresses, gethcommon.HexToAddress(contractAddress)) - } - } - - for chainID := range erc721TokenRequirements { - erc721ChainIDsMap[chainID] = true - } - chainIDsForERC20 := calculateChainIDsSet(accountsAndChainIDs, erc20ChainIDsMap) chainIDsForERC721 := calculateChainIDsSet(accountsAndChainIDs, erc721ChainIDsMap) @@ -260,7 +269,7 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke accountsChainIDsCombinations := make(map[gethcommon.Address]map[uint64]bool) - for _, tokenPermission := range permissions { + for _, tokenPermission := range permissionsParsedData.Permissions { permissionRequirementsMet := true response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{Role: tokenPermission.Type} @@ -435,3 +444,64 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke return response, nil } + +func preParsedPermissionsData(permissions []*CommunityTokenPermission) *PreParsedPermissionsData { + erc20TokenRequirements, erc721TokenRequirements, _ := ExtractTokenCriteria(permissions) + + erc20ChainIDsMap := make(map[uint64]bool) + erc721ChainIDsMap := make(map[uint64]bool) + + erc20TokenAddresses := make([]gethcommon.Address, 0) + + // figure out chain IDs we're interested in + for chainID, tokens := range erc20TokenRequirements { + erc20ChainIDsMap[chainID] = true + for contractAddress := range tokens { + erc20TokenAddresses = append(erc20TokenAddresses, gethcommon.HexToAddress(contractAddress)) + } + } + + for chainID := range erc721TokenRequirements { + erc721ChainIDsMap[chainID] = true + } + + return &PreParsedPermissionsData{ + Erc721TokenRequirements: erc721TokenRequirements, + Erc20TokenAddresses: erc20TokenAddresses, + Erc20ChainIDsMap: erc20ChainIDsMap, + Erc721ChainIDsMap: erc721ChainIDsMap, + } +} + +func preParsedCommunityPermissionsData(permissions []*CommunityTokenPermission) *PreParsedCommunityPermissionsData { + if len(permissions) == 0 { + return nil + } + + return &PreParsedCommunityPermissionsData{ + Permissions: permissions, + PreParsedPermissionsData: preParsedPermissionsData(permissions), + } +} + +func PreParsePermissionsData(permissions map[string]*CommunityTokenPermission) (map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData, map[string]*PreParsedCommunityPermissionsData) { + becomeMemberPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_MEMBER) + becomeAdminPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_ADMIN) + becomeTokenMasterPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER) + + viewOnlyPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) + viewAndPostPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) + channelPermissions := append(viewAndPostPermissions, viewOnlyPermissions...) + + communityPermissionsPreParsedData := make(map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData) + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER] = preParsedCommunityPermissionsData(becomeMemberPermissions) + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN] = preParsedCommunityPermissionsData(becomeAdminPermissions) + communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER] = preParsedCommunityPermissionsData(becomeTokenMasterPermissions) + + channelPermissionsPreParsedData := make(map[string]*PreParsedCommunityPermissionsData) + for _, channelPermission := range channelPermissions { + channelPermissionsPreParsedData[channelPermission.Id] = preParsedCommunityPermissionsData([]*CommunityTokenPermission{channelPermission}) + } + + return communityPermissionsPreParsedData, channelPermissionsPreParsedData +} diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index 1aed19ef9..fb3f841af 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -2055,3 +2055,59 @@ func (p *Persistence) getDecryptedCommunityDescriptionByID(tx *sql.Tx, community return nil, err } } + +func (p *Persistence) GetCommunityRequestsToJoinRevealedAddresses(communityID []byte) (map[string][]*protobuf.RevealedAccount, error) { + accounts := make(map[string][]*protobuf.RevealedAccount) + + rows, err := p.db.Query(` + SELECT r.public_key, + a.address, a.chain_ids, a.is_airdrop_address, a.signature + FROM communities_requests_to_join r + LEFT JOIN communities_requests_to_join_revealed_addresses a ON r.id = a.request_id + WHERE r.community_id = ? AND r.state = ?`, communityID, RequestToJoinStateAccepted) + + if err != nil { + if err == sql.ErrNoRows { + return accounts, nil + } + return nil, err + } + + defer rows.Close() + + for rows.Next() { + var rawPublicKey sql.NullString + var address sql.NullString + var chainIDsStr sql.NullString + var isAirdropAddress sql.NullBool + var signature sql.RawBytes + + err = rows.Scan(&rawPublicKey, &address, &chainIDsStr, &isAirdropAddress, &signature) + if err != nil { + return nil, err + } + + if !rawPublicKey.Valid { + return nil, errors.New("GetCommunityRequestsToJoinRevealedAddresses: invalid public key") + } + + publicKey := rawPublicKey.String + + revealedAccount, err := toRevealedAccount(address, chainIDsStr, isAirdropAddress, signature) + if err != nil { + return nil, err + } + + if revealedAccount == nil { + continue + } + + if _, exists := accounts[publicKey]; !exists { + accounts[publicKey] = []*protobuf.RevealedAccount{revealedAccount} + } else { + accounts[publicKey] = append(accounts[publicKey], revealedAccount) + } + } + + return accounts, nil +} diff --git a/protocol/communities/persistence_test.go b/protocol/communities/persistence_test.go index 1801dd880..1fbff0ace 100644 --- a/protocol/communities/persistence_test.go +++ b/protocol/communities/persistence_test.go @@ -1051,3 +1051,55 @@ func (s *PersistenceSuite) TestDecryptedCommunityCacheClock() { s.Require().NoError(err) s.Require().Equal(count, 1) } + +func (s *PersistenceSuite) TestGetCommunityRequestsToJoinRevealedAddresses() { + clock := uint64(time.Now().Unix()) + communityID := types.HexBytes{7, 7, 7, 7, 7, 7, 7, 7} + revealedAddress := "address1" + chainIds := []uint64{1, 2} + publicKey := common.PubkeyToHex(&s.identity.PublicKey) + signature := []byte("test") + + // No data in database + accounts, err := s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID) + s.Require().NoError(err) + _, exists := accounts[publicKey] + s.Require().False(exists) + + expectedRtj := &RequestToJoin{ + ID: types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8}, + PublicKey: publicKey, + Clock: clock, + CommunityID: communityID, + State: RequestToJoinStateAccepted, + RevealedAccounts: []*protobuf.RevealedAccount{ + { + Address: revealedAddress, + ChainIds: chainIds, + IsAirdropAddress: true, + Signature: signature, + }, + }, + } + + // Request to join was stored without revealed account + err = s.db.SaveRequestToJoin(expectedRtj) + s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error") + + // revealed account is absent + accounts, err = s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID) + s.Require().NoError(err, "RevealedAccounts empty, shouldn't give any error") + + _, exists = accounts[publicKey] + s.Require().False(exists) + + // save revealed accounts for the previous request to join + err = s.db.SaveRequestToJoinRevealedAddresses(expectedRtj.ID, expectedRtj.RevealedAccounts) + s.Require().NoError(err) + + accounts, err = s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID) + s.Require().NoError(err) + memberAccounts, exists := accounts[publicKey] + s.Require().True(exists) + s.Require().Len(memberAccounts, 1) +} diff --git a/protocol/communities/utils.go b/protocol/communities/utils.go index 25ddfcc18..eeb888b29 100644 --- a/protocol/communities/utils.go +++ b/protocol/communities/utils.go @@ -38,7 +38,7 @@ func ExtractTokenCriteria(permissions []*CommunityTokenPermission) (erc20TokenCr if isERC20 && !existsERC20 { erc20TokenCriteria[chainID] = make(map[string]*protobuf.TokenCriteria) } - + // TODO: check if we do not duplicate this due to ToLower case _, existsERC721 = erc721TokenCriteria[chainID][contractAddress] if isERC721 && !existsERC721 { erc721TokenCriteria[chainID][strings.ToLower(contractAddress)] = tokenRequirement diff --git a/protocol/communities_messenger_test.go b/protocol/communities_messenger_test.go index a70de4dea..2dd1353b1 100644 --- a/protocol/communities_messenger_test.go +++ b/protocol/communities_messenger_test.go @@ -3619,7 +3619,8 @@ func (t *testPermissionChecker) CheckPermissionToJoin(*communities.Community, [] return &communities.CheckPermissionsResponse{Satisfied: true}, nil } -func (t *testPermissionChecker) CheckPermissions(permissions []*communities.CommunityTokenPermission, accountsAndChainIDs []*communities.AccountChainIDsCombination, shortcircuit bool) (*communities.CheckPermissionsResponse, error) { + +func (t *testPermissionChecker) CheckPermissions(permissionsParsedData *communities.PreParsedCommunityPermissionsData, accountsAndChainIDs []*communities.AccountChainIDsCombination, shortcircuit bool) (*communities.CheckPermissionsResponse, error) { return &communities.CheckPermissionsResponse{Satisfied: true}, nil } diff --git a/protocol/communities_messenger_token_permissions_test.go b/protocol/communities_messenger_token_permissions_test.go index f20ed4008..78ad9052b 100644 --- a/protocol/communities_messenger_token_permissions_test.go +++ b/protocol/communities_messenger_token_permissions_test.go @@ -7,11 +7,13 @@ import ( "errors" "fmt" "math/big" + "strconv" "strings" "sync" "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/suite" "go.uber.org/zap" @@ -2011,3 +2013,122 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestResendEncryptionKeyOnBac s.Require().NoError(err) s.Require().Len(response.Messages(), 1) } + +func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberPermissionsPerformance() { + // This test is created for a performance degradation tracking for reevaluateMember permissions + // current scenario mostly track channels permissions reevaluating, but feel free to expand it to + // other scenarios or test you performance improvements + + // in average, it took nearly 100-105 ms to check one permission for a current scenario: + // - 10 members + // - 10 channels + // - one permission (channel permission for all 10 channels is set up) + + // currently, adding any new permission to test must twice the current test average time + + community, chat := s.createCommunity() + + community, err := s.owner.communitiesManager.GetByID(community.ID()) + s.Require().NoError(err) + s.Require().Len(community.Chats(), 2) + + requestToJoin := &communities.RequestToJoin{ + Clock: uint64(time.Now().Unix()), + CommunityID: community.ID(), + State: communities.RequestToJoinStateAccepted, + RevealedAccounts: []*protobuf.RevealedAccount{ + { + Address: bobAddress, + ChainIds: []uint64{testChainID1}, + IsAirdropAddress: true, + Signature: []byte("test"), + }, + }, + } + communityRole := []protobuf.CommunityMember_Roles{} + + keysCount := 10 + + for i := 0; i < keysCount; i++ { + privateKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + memberPubKeyStr := common.PubkeyToHex(&privateKey.PublicKey) + requestId := communities.CalculateRequestID(memberPubKeyStr, community.ID()) + requestToJoin.ID = requestId + requestToJoin.PublicKey = memberPubKeyStr + + err = s.owner.communitiesManager.SaveRequestToJoin(requestToJoin) + s.Require().NoError(err) + err = s.owner.communitiesManager.SaveRequestToJoinRevealedAddresses(requestId, requestToJoin.RevealedAccounts) + s.Require().NoError(err) + _, err = community.AddMember(&privateKey.PublicKey, communityRole) + s.Require().NoError(err) + _, err = community.AddMemberToChat(chat.CommunityChatID(), &privateKey.PublicKey, communityRole, protobuf.CommunityMember_CHANNEL_ROLE_POSTER) + s.Require().NoError(err) + } + + s.Require().Equal(community.MembersCount(), keysCount+1) // 1 is owner + + chatsCount := 8 // in total will be 10, 2 channels were created during creating the community + + for i := 0; i < chatsCount; i++ { + newChat := &protobuf.CommunityChat{ + Permissions: &protobuf.CommunityPermissions{ + Access: protobuf.CommunityPermissions_AUTO_ACCEPT, + }, + Identity: &protobuf.ChatIdentity{ + DisplayName: "name-" + strconv.Itoa(i), + Description: "", + }, + } + + chatID := uuid.New().String() + _, err = community.CreateChat(chatID, newChat) + s.Require().NoError(err) + } + + s.Require().Len(community.Chats(), chatsCount+2) // 2 chats were created during community creation + + err = s.owner.communitiesManager.SaveCommunity(community) + s.Require().NoError(err) + + // setup view channel permission + channelPermissionRequest := requests.CreateCommunityTokenPermission{ + CommunityID: community.ID(), + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: []*protobuf.TokenCriteria{ + &protobuf.TokenCriteria{ + Type: protobuf.CommunityTokenType_ERC20, + ContractAddresses: map[uint64]string{testChainID1: "0x123"}, + Symbol: "TEST", + AmountInWei: "100000000000000000000", + Decimals: uint64(18), + }, + }, + ChatIds: community.ChatIDs(), + } + + s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0]) + defer s.resetMockedBalances() // reset mocked balances, this test in run with different test cases + + // create permission using communitiesManager in order not to launch blocking reevaluation loop + community, _, err = s.owner.communitiesManager.CreateCommunityTokenPermission(&channelPermissionRequest) + s.Require().NoError(err) + s.Require().Len(community.TokenPermissions(), 1) + + for _, ids := range community.ChatIDs() { + s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), ids)) + } + + // force owner to reevaluate channel members + // in production it will happen automatically, by periodic check + start := time.Now() + _, _, err = s.owner.communitiesManager.ReevaluateMembers(community.ID()) + s.Require().NoError(err) + + elapsed := time.Since(start) + + fmt.Println("ReevaluateMembers Time: ", elapsed) + s.Require().Less(elapsed.Seconds(), 2.0) +}