// verifiedVerdict provides functions to determine the current moderator consensus verdict for a particular identity/message/profile // These verdicts are calculated based on the downloaded moderator reviews, and are thus verified package verifiedVerdict // We cannot determine the verified consensus verdict of a message/profile without knowing its metadata. // See package contentMetadata for an explanation. // Possible verdicts: // -Identity: Banned/Not Banned // -Profile/Message: Banned/Approved/Undecided import "seekia/internal/contentMetadata" import "seekia/internal/identity" import "seekia/internal/encoding" import "seekia/internal/moderation/bannedModeratorConsensus" import "seekia/internal/moderation/enabledModerators" import "seekia/internal/moderation/moderatorScores" import "seekia/internal/moderation/reviewStorage" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "seekia/internal/network/backgroundDownloads" import "seekia/internal/profiles/readProfiles" import "errors" //Outputs: // -bool: Client is downloading the required reviews/moderator profiles // -bool: Parameters Exist // -bool: Identity is banned // -int: Number of eligible moderators who have banned the identity // -float64: Ban Advocates identity score sum (Sum of identity score of all eligible moderators who banned the identity) // -[][16]byte: List of all ban advocates (Eligible and banned) // -error func GetVerifiedIdentityVerdict(identityHash [16]byte, networkType byte)(bool, bool, bool, int, float64, [][16]byte, error){ clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(identityHash) if (err != nil) { return false, false, false, 0, 0, nil, err } if (clientIsDownloadingRequiredReviews == false){ return false, false, false, 0, 0, nil, nil } appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { return false, false, false, 0, 0, nil, err } if (appNetworkType != networkType){ // We are not downloading reviews for this network type. return false, false, false, 0, 0, nil, nil } //TODO: Add parameters check parametersExist := true if (parametersExist == false){ // We do not have moderation parameters. We cannot determine verdict consensus return true, false, false, 0, 0, nil, nil } banAdvocatesMap, err := reviewStorage.GetIdentityBanAdvocatesMap(identityHash, networkType) if (err != nil) { return false, false, false, 0, 0, nil, err } numberOfEligibleBanAdvocates := 0 eligibleBanAdvocatesScoreSum := float64(0) allEnabledBanAdvocatesList := make([][16]byte, 0) for moderatorIdentityHash, _ := range banAdvocatesMap{ moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, networkType) if (err != nil){ return false, false, false, 0, 0, nil, err } if (moderatorIsEnabled == false){ // We will disregard all of this moderators reviews continue } allEnabledBanAdvocatesList = append(allEnabledBanAdvocatesList, moderatorIdentityHash) downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, networkType) if (err != nil) { return false, false, false, 0, 0, nil, err } if (downloadingRequiredData == false){ return false, true, false, 0, 0, nil, nil } if (parametersExist == false){ return true, false, false, 0, 0, nil, nil } if (moderatorIsBanned == true){ continue } scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash) if (err != nil) { return false, false, false, 0, 0, nil, err } if (scoreIsKnown == false || scoreIsSufficient == false){ continue } numberOfEligibleBanAdvocates += 1 eligibleBanAdvocatesScoreSum += moderatorScore } //Outputs: // -bool: Downloading required data // -bool: Parameters exist // -bool: Identity is banned // -error getBanConsensusVerdict := func()(bool, bool, bool, error){ identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash) if (err != nil){ identityHashHex := encoding.EncodeBytesToHexString(identityHash[:]) return false, false, false, errors.New("getBanConsensusVerdict reached with invalid identityHash: " + identityHashHex) } if (identityType != "Moderator"){ //TODO: Retrieve this variable from parameters minimumBanAdvocatesNeeded := 3 if (numberOfEligibleBanAdvocates < minimumBanAdvocatesNeeded){ return true, true, false, nil } return true, true, true, nil } // identityType == "Moderator" // For moderators, the banning process is more complicated // Moderators must have a higher score to ban another moderator // We use another package to calculate the result downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, identityHash, networkType) if (err != nil) { return false, false, false, err } if (downloadingRequiredData == false){ return false, false, false, nil } if (parametersExist == false){ return true, false, false, nil } return true, true, moderatorIsBanned, nil } downloadingRequiredData, parametersExist, banConsensusVerdict, err := getBanConsensusVerdict() if (err != nil) { return false, false, false, 0, 0, nil, err } if (downloadingRequiredData == false){ return false, true, false, 0, 0, nil, nil } if (parametersExist == false){ return true, false, false, 0, 0, nil, nil } return true, true, banConsensusVerdict, numberOfEligibleBanAdvocates, eligibleBanAdvocatesScoreSum, allEnabledBanAdvocatesList, nil } // This function returns if the provided profile is banned. // This function does not take into account if the profile author is banned. // A profile with an insufficient number of reviews/reviewers can be "Undecided" // Host/Moderator "Undecided" profiles are still considered viewable, only Mate profiles must be "Approved" to be viewable //Outputs: // -bool: Profile is disabled // -bool: Profile metadata is known // -int: Profile version // -byte: Profile network type // -[16]byte: Profile Identity Hash // -map[int][27]byte: Profile attribute hashes map (Attribute identifier -> Attribute hash) // -bool: Client is downloading required reviews and moderator profiles // -bool: Parameters exist // -string: Moderator consensus verdict ("Approved"/"Banned"/"Undecided") // -int: Number of eligible moderators who have approved the profile // -int: Number of eligible moderators who have banned the profile (including a single attribute) // -float64: Approve Score Sum (sum of identity scores for all eligible moderators who approved the entire profile) // -float64: Ban Score Sum (sum of identity scores for all eligible moderators who banned the profile, including a single attribute) // -map[int]int: Attribute Identifier -> Number of eligible approve advocates // -map[int]int: Attribute identifier -> Number of eligible ban advocates // -This does not include users who banned the full profile without describing which attribute was unruleful // -map[int]float64: Attribute identifier -> Sum of all eligible approve advocate identity scores // -This includes users who banned the full profile. // -map[int]float64: Attribute identifier -> Sum of all eligible ban advocate identity scores // -This includes moderators who banned the entire profile // -[][16]byte: List of all moderators who have approved the profile (Eligible and banned) // -This does not include moderators who have only approved some attributes. // -This only includes moderators who have approved the full profile. // -[][16]byte: List of all moderators who have banned the profile (eligible and banned) // -This includes moderators who have banned only 1 attribute. // -int: Number of moderators who have banned the full profile (eligible and banned) // -This only includes moderators who have banned the full profile (not including ones who only banned attributes) // -map[int][][16]byte: Map of attribute identifier -> List of moderators who have approved attribute (eligible and banned) // -map[int][][16]byte: Map of Attribute Identifier -> List of moderators who have banned attribute (eligible and banned) // -This does not include moderators who have banned the entire profile without describing which attribute was unruleful // -error func GetVerifiedProfileVerdict(profileHash [28]byte)(bool, bool, int, byte, [16]byte, map[int][27]byte, bool, bool, string, int, int, float64, float64, map[int]int, map[int]int, map[int]float64, map[int]float64, [][16]byte, [][16]byte, int, map[int][][16]byte, map[int][][16]byte, error){ _, profileIsDisabled, err := readProfiles.ReadProfileHashMetadata(profileHash) if (err != nil){ profileHashHex := encoding.EncodeBytesToHexString(profileHash[:]) return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, errors.New("GetVerifiedProfileVerdict called with invalid profileHash: " + profileHashHex) } //TODO: Add parameters check parametersExist := true metadataExists, profileVersion, profileNetworkType, profileIdentityHash, _, profileIsDisabledB, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash) if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (metadataExists == false){ // We do not have profile metadata, we cannot determine moderator consensus return profileIsDisabled, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } if (profileIsDisabled != profileIsDisabledB){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, errors.New("GetProfileMetadata returning different profileIsDisabled status than ReadProfileHashMetadata.") } if (profileIsDisabled == true){ return true, true, profileVersion, profileNetworkType, profileIdentityHash, nil, true, parametersExist, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(profileIdentityHash) if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (clientIsDownloadingRequiredReviews == false){ // We cannot determine verdict. Required reviews are not being downloaded. return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, false, parametersExist, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } if (parametersExist == false){ // We dont have parameters. We cannot determine status. return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } // This stores all moderator identity hashes who reviewed the profile/attributes // We use a map to avoid duplicates // Map Structure: Reviewer identity hash -> Nothing allReviewersMap := make(map[[16]byte]struct{}) type fullProfileVerdictInfo struct{ // Verdict for full profile ("Approve"/"Ban") Verdict string // Time of verdict for full profile VerdictTime int64 } // Map Structure: Author Identity hash -> fullProfileVerdictInfo fullProfileVerdictsInfoMap := make(map[[16]byte]fullProfileVerdictInfo) type attributeReviewObject struct{ AttributeIdentifier int // Verdict for the attribute ("Approve"/"Ban") Verdict string // Time at which the verdict was made VerdictTime int64 } // Map Structure: Reviewer identity hash -> List of attribute review objects attributeReviewsMap := make(map[[16]byte][]attributeReviewObject) // First we add full profile reviews fullProfileApproveAdvocatesMap, fullProfileBanAdvocatesMap, err := reviewStorage.GetProfileVerdictMaps(profileHash, profileNetworkType, false, nil) if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } addFullProfileVerdictsToMaps := func(reviewersMap map[[16]byte]int64, verdict string)error{ for reviewerIdentityHash, verdictTime := range reviewersMap{ allReviewersMap[reviewerIdentityHash] = struct{}{} newVerdictInfoObject := fullProfileVerdictInfo{ Verdict: verdict, VerdictTime: verdictTime, } _, exists := fullProfileVerdictsInfoMap[reviewerIdentityHash] if (exists == true){ return errors.New("Trying to add reviewer to fullProfileVerdictsInfoMap and reviewer entry already exists.") } fullProfileVerdictsInfoMap[reviewerIdentityHash] = newVerdictInfoObject } return nil } err = addFullProfileVerdictsToMaps(fullProfileApproveAdvocatesMap, "Approve") if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } err = addFullProfileVerdictsToMaps(fullProfileBanAdvocatesMap, "Ban") if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } // Now we add profile attribute reviews for attributeIdentifier, attributeHash := range profileAttributeHashesMap{ addAttributeVerdictsToMap := func(reviewersMap map[[16]byte]int64, verdict string)error{ for reviewerIdentityHash, verdictTime := range reviewersMap{ allReviewersMap[reviewerIdentityHash] = struct{}{} newAttributeReviewObject := attributeReviewObject{ AttributeIdentifier: attributeIdentifier, Verdict: verdict, VerdictTime: verdictTime, } existingReviewObjectsList, exists := attributeReviewsMap[reviewerIdentityHash] if (exists == false){ newAttributeReviewObjectsList := []attributeReviewObject{newAttributeReviewObject} attributeReviewsMap[reviewerIdentityHash] = newAttributeReviewObjectsList } else { newAttributeReviewObjectsList := append(existingReviewObjectsList, newAttributeReviewObject) attributeReviewsMap[reviewerIdentityHash] = newAttributeReviewObjectsList } } return nil } approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetProfileAttributeVerdictMaps(attributeHash, profileNetworkType, false, nil) if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } err = addAttributeVerdictsToMap(approveAdvocatesMap, "Approve") if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } err = addAttributeVerdictsToMap(banAdvocatesMap, "Ban") if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } } // This keeps track of the number of eligible moderators who approved the full profile numberOfEligibleApproveAdvocates := 0 // This keeps track of the number of eligible moderators who banned the profile numberOfEligibleBanAdvocates := 0 // This is the sum of all identity scores of all eligible moderators who approved the full profile eligibleApproveAdvocateScoresSum := float64(0) // This is the sum of all identity scores of all eligible moderators who banned the profile eligibleBanAdvocateScoresSum := float64(0) // We need these maps when determining final verdict // Map Structure: Attribute identifier -> Number of eligible approve advocates eligibleAttributeApproveAdvocateCountsMap := make(map[int]int) // Map Structure: Attribute identifier -> Number of eligible ban advocates (not including full profile bans) eligibleAttributeBanAdvocateCountsMap := make(map[int]int) // This is a map that keeps track of each attribute's approve and ban weight // This is used to determine if the profile is approved or banned // Each approve/ban increases attribute weight by the moderator's identity score // Approving the entire profile adds to each attribute approve weight // Banning the entire profile adds to each attribute ban weight // It ignores the reviews of banned/ineligible moderators // Map Structure: Attribute identifier -> Attribute weight attributeApproveWeightsMap := make(map[int]float64) attributeBanWeightsMap := make(map[int]float64) // This is a list of all moderators who have approved the profile (Eligible and banned) // This only includes moderators who have approved the full profile. // This does not include moderators who have only approved some attributes. allProfileApproveAdvocatesList := make([][16]byte, 0) // This is a list of all moderators who have banned the profile (eligible and banned) // This includes moderators who have banned only 1 attribute. allProfileBanAdvocatesList := make([][16]byte, 0) // This keeps track of the number of moderators who have banned the full profile (eligible and banned) numberOfFullProfileBanAdvocates := 0 // -This includes moderators who have approved the full profile. // Map Structure: Attribute identifier -> List of moderators who have approved attribute (eligible and banned) allAttributeApproveAdvocatesMap := make(map[int][][16]byte) // -This does not include moderators who have only banned the entire profile, but not the specified attribute // Map Structure: Attribute Identifier -> List of moderators who have banned attribute (eligible and banned) allAttributeBanAdvocatesMap := make(map[int][][16]byte) // Now we iterate through each moderator to populate the variables we just created for moderatorIdentityHash, _ := range allReviewersMap{ moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, profileNetworkType) if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (moderatorIsEnabled == false){ // We will disregard all of this moderators reviews continue } downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, profileNetworkType) if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (downloadingRequiredData == false){ // We cannot determine verdict. Required reviews are not being downloaded. return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, false, true, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } if (parametersExist == false){ // We dont have parameters. We cannot determine status. return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil } //Outputs: // -bool: Moderator is eligible // -float64: Moderator identity score // -error checkIfModeratorIsEligible := func()(bool, float64, error){ if (moderatorIsBanned == true){ return false, 0, nil } scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash) if (err != nil) { return false, 0, err } if (scoreIsKnown == false || scoreIsSufficient == false){ return false, 0, nil } return true, moderatorScore, nil } moderatorIsEligible, moderatorScore, err := checkIfModeratorIsEligible() if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } // We use the function below to deal with the possibility of a conflict between the profile/attribute verdicts // Outputs: // -bool: Full profile verdict exists // -string: Full profile verdict ("Approve"/"Ban") // -map[int]string: Attribute identifier -> Attribute verdict ("Approve"/"Ban") // -error getModeratorVerdicts := func()(bool, string, map[int]string, error){ fullProfileReviewInfoObject, fullProfileReviewExists := fullProfileVerdictsInfoMap[moderatorIdentityHash] attributeReviewObjectsList, anyAttributeReviewExists := attributeReviewsMap[moderatorIdentityHash] if (fullProfileReviewExists == false && anyAttributeReviewExists == false){ return false, "", nil, errors.New("allReviewersMap contains reviewer without any reviews.") } if (fullProfileReviewExists == true && anyAttributeReviewExists == false){ fullProfileReviewVerdict := fullProfileReviewInfoObject.Verdict if (fullProfileReviewVerdict == "Approve"){ // Full profile is approved. All attributes are approved // Map Structure: Attribute identifier -> Attribute verdict attributeVerdictsMap := make(map[int]string) for attributeIdentifier, _ := range profileAttributeHashesMap{ attributeVerdictsMap[attributeIdentifier] = "Approve" } return true, "Approve", attributeVerdictsMap, nil } emptyMap := make(map[int]string) return true, "Ban", emptyMap, nil } if (fullProfileReviewExists == false && anyAttributeReviewExists == true){ // Map Structure: Attribute identifier -> Attribute verdict attributeReviewsMap := make(map[int]string) for _, attributeReviewObject := range attributeReviewObjectsList{ attributeIdentifier := attributeReviewObject.AttributeIdentifier attributeVerdict := attributeReviewObject.Verdict attributeReviewsMap[attributeIdentifier] = attributeVerdict } return false, "", attributeReviewsMap, nil } // The moderator has reviewed the full profile and at least 1 of the profile's attributes // We have to take into account that newer reviews will cancel out older reviews // // These are the two possible conflicts: // 1. if a user bans a profile attribute, and then later approves the entire profile, all of their // previous attribute ban reviews for the profile are discarded // 2. If a user approves a full profile, and then later bans an attribute, their full profile approval review is discarded fullProfileReviewVerdict := fullProfileReviewInfoObject.Verdict fullProfileVerdictTime := fullProfileReviewInfoObject.VerdictTime // Outputs: // -bool: Full profile verdict exists // -string: Full profile verdict // -error getFullProfileVerdict := func()(bool, string, error){ if (fullProfileReviewVerdict == "Ban"){ return true, "Ban", nil } for _, attributeReviewObject := range attributeReviewObjectsList{ attributeVerdict := attributeReviewObject.Verdict if (attributeVerdict == "Ban"){ attributeVerdictTime := attributeReviewObject.VerdictTime if (attributeVerdictTime > fullProfileVerdictTime){ // The moderator banned an attribute after approving the full profile // We discard their full profile approval return false, "", nil } } } return true, "Approve", nil } fullProfileReviewExists, fullProfileVerdict, err := getFullProfileVerdict() if (err != nil) { return false, "", nil, err } if (fullProfileReviewExists == true && fullProfileVerdict == "Approve"){ // Full profile is approved. All attributes are approved // Map Structure: Attribute identifier -> Attribute verdict attributeVerdictsMap := make(map[int]string) for attributeIdentifier, _ := range profileAttributeHashesMap{ attributeVerdictsMap[attributeIdentifier] = "Approve" } return true, "Approve", attributeVerdictsMap, nil } // Map Structure: Attribute identifier -> Attribute verdict attributeVerdictsMap := make(map[int]string) for _, attributeReviewObject := range attributeReviewObjectsList{ attributeIdentifier := attributeReviewObject.AttributeIdentifier attributeVerdict := attributeReviewObject.Verdict attributeVerdictsMap[attributeIdentifier] = attributeVerdict } return fullProfileReviewExists, fullProfileVerdict, attributeVerdictsMap, nil } fullProfileReviewExists, fullProfileVerdict, attributeVerdictsMap, err := getModeratorVerdicts() if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (fullProfileReviewExists == true && fullProfileVerdict == "Ban"){ numberOfFullProfileBanAdvocates += 1 } //Outputs: // -bool: Verdict exists // -string: Verdict getModeratorProfileVerdict := func()(bool, string){ if (fullProfileReviewExists == true){ return true, fullProfileVerdict } // A moderator who has banned a single attribute is considered to have banned the entire profile for _, attributeVerdict := range attributeVerdictsMap{ if (attributeVerdict == "Ban"){ return true, "Ban" } } return false, "" } profileVerdictExists, profileVerdict := getModeratorProfileVerdict() if (profileVerdictExists == true){ if (profileVerdict == "Approve"){ allProfileApproveAdvocatesList = append(allProfileApproveAdvocatesList, moderatorIdentityHash) if (moderatorIsEligible == true){ numberOfEligibleApproveAdvocates += 1 eligibleApproveAdvocateScoresSum += moderatorScore } } else { allProfileBanAdvocatesList = append(allProfileBanAdvocatesList, moderatorIdentityHash) if (moderatorIsEligible == true){ numberOfEligibleBanAdvocates += 1 eligibleBanAdvocateScoresSum += moderatorScore } } } for attributeIdentifier, attributeVerdict := range attributeVerdictsMap{ addIdentityHashToMapEntryList := func(inputMap map[int][][16]byte){ currentList, exists := inputMap[attributeIdentifier] if (exists == false){ inputMap[attributeIdentifier] = [][16]byte{moderatorIdentityHash} } else { currentList = append(currentList, moderatorIdentityHash) inputMap[attributeIdentifier] = currentList } } if (attributeVerdict == "Approve"){ addIdentityHashToMapEntryList(allAttributeApproveAdvocatesMap) if (moderatorIsEligible == true){ eligibleAttributeApproveAdvocateCountsMap[attributeIdentifier] += 1 } } else { addIdentityHashToMapEntryList(allAttributeBanAdvocatesMap) if (moderatorIsEligible == true){ eligibleAttributeBanAdvocateCountsMap[attributeIdentifier] += 1 } } } if (moderatorIsEligible == true){ // Now we add to attributeApproveWeightsMap and attributeBanWeightsMap if (fullProfileReviewExists == true && fullProfileVerdict == "Ban"){ // We add weight for every attribute entry for attributeIdentifier, _ := range profileAttributeHashesMap{ attributeBanWeightsMap[attributeIdentifier] += moderatorScore } } else { // We add to each attribute, only when user approved/banned them for attributeIdentifier, attributeVerdict := range attributeVerdictsMap{ if (attributeVerdict == "Approve"){ attributeApproveWeightsMap[attributeIdentifier] += moderatorScore } else { attributeBanWeightsMap[attributeIdentifier] += moderatorScore } } } } } userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(profileIdentityHash) if (err != nil){ return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } if (len(allProfileApproveAdvocatesList) == 0 && len(allProfileBanAdvocatesList) == 0 && len(allAttributeApproveAdvocatesMap) == 0 && len(allAttributeBanAdvocatesMap) == 0){ // No reviews exist. // Profile is undecided. emptyMapA := make(map[int]int) emptyMapB := make(map[int]int) emptyMapC := make(map[int]float64) emptyMapD := make(map[int]float64) emptyListA := make([][16]byte, 0) emptyListB := make([][16]byte, 0) emptyMapE := make(map[int][][16]byte) emptyMapF := make(map[int][][16]byte) return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, true, "Undecided", 0, 0, 0, 0, emptyMapA, emptyMapB, emptyMapC, emptyMapD, emptyListA, emptyListB, 0, emptyMapE, emptyMapF, nil } //TODO: Retrieve from parameters minimumAttributeApproveAdvocates_Mate := 0 // Applies to all non-canonical attributes even if they are not banned minimumAttributeApproveAdvocates_Host := 0 // Only used if attribute/profile is banned minimumAttributeApproveAdvocates_Moderator := 0 // Only used if attribute/profile is banned getMinimumNeededApprovers := func()int{ if (userIdentityType == "Mate"){ return minimumAttributeApproveAdvocates_Mate } if (userIdentityType == "Host"){ return minimumAttributeApproveAdvocates_Host } // userIdentityType == "Moderator" return minimumAttributeApproveAdvocates_Moderator } minimumNeededApprovers := getMinimumNeededApprovers() //TODO: Retrieve from parameters minimumApprovedRatio_Mate := float64(1.5) minimumApprovedRatio_Host := float64(1.7) minimumApprovedRatio_Moderator := float64(1.8) getMinimumApprovedRatio := func()float64{ if (userIdentityType == "Mate"){ return minimumApprovedRatio_Mate } if (userIdentityType == "Host"){ return minimumApprovedRatio_Host } // userIdentityType == "Moderator" return minimumApprovedRatio_Moderator } minimumApprovedRatio := getMinimumApprovedRatio() getVerdictConsensus := func()(string, error){ // This will be true if any non-canonical attribute is undecided nonCanonicalUndecidedExists := false for attributeIdentifier, attributeHash := range profileAttributeHashesMap{ _, attributeIsCanonical, err := readProfiles.ReadAttributeHashMetadata(attributeHash) if (err != nil){ attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:]) return "", errors.New("profileAttributeHashesMap contains invalid attribute hash: " + attributeHashHex) } getAttributeApproveWeight := func()float64{ attributeApproveWeight, approveWeightExists := attributeApproveWeightsMap[attributeIdentifier] if (approveWeightExists == false){ return 0 } return attributeApproveWeight } getAttributeBanWeight := func()float64{ attributeBanWeight, banWeightExists := attributeBanWeightsMap[attributeIdentifier] if (banWeightExists == false){ return 0 } return attributeBanWeight } approveWeight := getAttributeApproveWeight() banWeight := getAttributeBanWeight() if (approveWeight < 0 || banWeight < 0){ // This should never happen. return "", errors.New("Attribute approve/ban weight is negative.") } if (approveWeight == 0 && banWeight == 0){ if (userIdentityType == "Mate" && attributeIsCanonical == false){ // Attribute status is Undecided nonCanonicalUndecidedExists = true } continue } if (approveWeight == 0 && banWeight > 0){ // The attribute is banned, thus the entire profile is banned. return "Banned", nil } getNumberOfAttributeApproveAdvocates := func()int{ numberOfAttributeApproveAdvocates, exists := eligibleAttributeApproveAdvocateCountsMap[attributeIdentifier] if (exists == false){ return 0 } return numberOfAttributeApproveAdvocates } if (approveWeight > 0 && banWeight == 0){ if (userIdentityType == "Mate" && attributeIsCanonical == false){ // The attribute must be approved by the minimum number of reviewers numberOfAttributeApproveAdvocates := getNumberOfAttributeApproveAdvocates() if (numberOfAttributeApproveAdvocates < minimumAttributeApproveAdvocates_Mate){ // Attribute status is Undecided // The attribute is not approved by the minimum number of moderators. nonCanonicalUndecidedExists = true } } continue } // approveWeight > 0 && banWeight > 0 // There is at least 1 ban on the attribute/profile // We must make sure that the minimum number of approvers have approved the attribute numberOfAttributeApproveAdvocates := getNumberOfAttributeApproveAdvocates() if (numberOfAttributeApproveAdvocates < minimumNeededApprovers){ // The attribute has at least 1 ban advocate, and does not have the minimum required approve advocates. return "Banned", nil } // We determine what the verdict is approveRatio := approveWeight/banWeight if (approveRatio < minimumApprovedRatio){ return "Banned", nil } // This attribute has passed. We continue to the next attribute } // All attributes are either approved or undecided if (nonCanonicalUndecidedExists == true){ // Non-mate profiles do not need non-canonical attributes approved, if there are no ban reviews of the attribute // For Mate profiles, all non-canonical attributes must be approved for the profile to be approved return "Undecided", nil } // All attributes passed. return "Approved", nil } verdictConsensus, err := getVerdictConsensus() if (err != nil) { return false, false, 0, 0, [16]byte{}, nil, false, false, "", 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, 0, nil, nil, err } return false, true, profileVersion, profileNetworkType, profileIdentityHash, profileAttributeHashesMap, true, true, verdictConsensus, numberOfEligibleApproveAdvocates, numberOfEligibleBanAdvocates, eligibleApproveAdvocateScoresSum, eligibleBanAdvocateScoresSum, eligibleAttributeApproveAdvocateCountsMap, eligibleAttributeBanAdvocateCountsMap, attributeApproveWeightsMap, attributeBanWeightsMap, allProfileApproveAdvocatesList, allProfileBanAdvocatesList, numberOfFullProfileBanAdvocates, allAttributeApproveAdvocatesMap, allAttributeBanAdvocatesMap, nil } //Outputs: // -bool: Message metadata is known // -int: Message version // -byte: Message network type // -[10]byte: Message inbox // -[25]byte: Message cipher key hash // -bool: Client is downloading required reviews and moderator profiles // -bool: Parameters exist // -string: Message verdict ("Approved"/"Banned"/"Undecided") // -int: Number of eligible moderators who have approved the message // -int: Number of eligible moderators who have banned the message // -float64: Approve identity score sum (sum of identity scores of all eligible moderators who approved the message) // -float64: Ban identity score sum (sum of identity scores of all eligible moderators who banned the message) // -[][16]byte: List of approve advocates (eligible and banned) // -[][16]byte: List of ban advocates (eligible and banned) // -error func GetVerifiedMessageVerdict(messageHash [26]byte)(bool, int, byte, [10]byte, [25]byte, bool, bool, string, int, int, float64, float64, [][16]byte, [][16]byte, error){ messageMetadataExists, messageVersion, messageNetworkType, _, messageInbox, messageCipherKeyHash, err := contentMetadata.GetMessageMetadata(messageHash) if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err } if (messageMetadataExists == false){ // We do not know message metadata, so we cannot retrieve message moderated status // Message must be downloaded at some point to get the message metadata return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, nil } clientIsDownloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineMessageVerdict(messageNetworkType, messageInbox, true, messageHash) if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err } if (clientIsDownloadingRequiredReviews == false){ return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil } //TODO: Add parameters check parametersExist := true if (parametersExist == false){ // We do not have parameters downloaded. We cannot determine consensus verdict return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil } approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetMessageVerdictMaps(messageHash, messageNetworkType, messageCipherKeyHash) if (err != nil) { return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err } if (len(approveAdvocatesMap) == 0 && len(banAdvocatesMap) == 0){ // There are no valid reviews for this message emptyListA := make([][16]byte, 0) emptyListB := make([][16]byte, 0) return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, true, "Undecided", 0, 0, 0, 0, emptyListA, emptyListB, nil } getEnabledModeratorsFromMap := func(inputModeratorsMap map[[16]byte]int64)([][16]byte, error){ enabledModeratorsList := make([][16]byte, 0) for moderatorIdentityHash, _ := range inputModeratorsMap{ moderatorIsEnabled, err := enabledModerators.CheckIfModeratorIsEnabled(true, moderatorIdentityHash, messageNetworkType) if (err != nil){ return nil, err } if (moderatorIsEnabled == false){ // We will disregard all of this moderators reviews continue } enabledModeratorsList = append(enabledModeratorsList, moderatorIdentityHash) } return enabledModeratorsList, nil } allEnabledApproveAdvocatesList, err := getEnabledModeratorsFromMap(approveAdvocatesMap) if (err != nil){ return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err } allEnabledBanAdvocatesList, err := getEnabledModeratorsFromMap(banAdvocatesMap) if (err != nil){ return false, 0, 0, [10]byte{}, [25]byte{}, false, false, "", 0, 0, 0, 0, nil, nil, err } //Outputs: // -bool: Downloading required data // -bool: Parameters exist // -int: Number of eligible moderators // -float64: Sum of all moderator scores // -error getEligibleModeratorsCountAndScoresSum := func(inputModeratorsList [][16]byte)(bool, bool, int, float64, error){ numberOfEligibleModerators := 0 eligibleModeratorScoresSum := float64(0) for _, moderatorIdentityHash := range inputModeratorsList{ downloadingRequiredData, parametersExist, moderatorIsBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(true, moderatorIdentityHash, messageNetworkType) if (err != nil) { return false, false, 0, 0, err } if (downloadingRequiredData == false){ return false, false, 0, 0, nil } if (parametersExist == false){ return false, true, 0, 0, nil } if (moderatorIsBanned == true){ continue } scoreIsKnown, moderatorScore, scoreIsSufficient, _, _, err := moderatorScores.GetModeratorIdentityScore(moderatorIdentityHash) if (err != nil) { return false, false, 0, 0, err } if (scoreIsKnown == false || scoreIsSufficient == false){ continue } numberOfEligibleModerators += 1 eligibleModeratorScoresSum += moderatorScore } return true, true, numberOfEligibleModerators, eligibleModeratorScoresSum, nil } downloadingRequiredData, parametersExist, numberOfEligibleApproveAdvocates, eligibleApproveAdvocatesScoreSum, err := getEligibleModeratorsCountAndScoresSum(allEnabledApproveAdvocatesList) if (downloadingRequiredData == false){ return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil } if (parametersExist == false){ return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil } downloadingRequiredData, parametersExist, numberOfEligibleBanAdvocates, eligibleBanAdvocatesScoreSum, err := getEligibleModeratorsCountAndScoresSum(allEnabledBanAdvocatesList) if (downloadingRequiredData == false){ return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, false, false, "", 0, 0, 0, 0, nil, nil, nil } if (parametersExist == false){ return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, false, "", 0, 0, 0, 0, nil, nil, nil } getMessageVerdictConsensus := func()string{ //TODO: Fix this to use parameters minimumBanAdvocatesCount := 3 minimumApproveAdvocatesCount := 3 if (numberOfEligibleBanAdvocates < minimumBanAdvocatesCount){ return "Undecided" } if (eligibleApproveAdvocatesScoreSum < eligibleBanAdvocatesScoreSum){ return "Banned" } if (numberOfEligibleApproveAdvocates >= minimumApproveAdvocatesCount){ return "Approved" } return "Undecided" } messageVerdictConsensus := getMessageVerdictConsensus() return true, messageVersion, messageNetworkType, messageInbox, messageCipherKeyHash, true, true, messageVerdictConsensus, numberOfEligibleApproveAdvocates, numberOfEligibleBanAdvocates, eligibleApproveAdvocatesScoreSum, eligibleBanAdvocatesScoreSum, allEnabledApproveAdvocatesList, allEnabledBanAdvocatesList, nil }