seekia/internal/moderation/verifiedVerdict/verifiedVerdict.go

983 lines
40 KiB
Go
Raw Normal View History

// 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
}