// eligibleHosts provides a function to get a list of eligible Seekia hosts // Eligible = not banned, not unreachable, not malicious, not disabled, funded, not blocked, and whose profiles are not expired package eligibleHosts // The list of hosts we return might not fulfill some of the eligibility requirements if not enough hosts are available who do. // For example, if a user has not opened their Seekia client since the host profile expiration time has passed, // all of the stored host profiles will be expired. // Thus, expired hosts will be included in the eligible hosts list when not enough hosts are available. // This is necessary to be able to download newer unexpired host profiles // If there are insufficient eligible-but-expired hosts, the function will return at least some blocked/banned/unreachable/malicious hosts. // This is because a malicious moderator could ban all hosts, or we could have falsely marked hosts as being unreachable // TODO: The network entry hosts, which are coded into the client and provided in the parameters // These hosts will always be eligible // They are the hosts that the client initially connects to, to discover other hosts //TODO: Store eligible hosts list in memory and refresh automatically using backgroundJobs? import "seekia/internal/helpers" import "seekia/internal/myBlockedUsers" import "seekia/internal/network/enabledHosts" import "seekia/internal/network/unreachableHosts" import "seekia/internal/network/maliciousHosts" import "seekia/internal/network/fundedStatus" import "seekia/internal/profiles/viewableProfiles" import "seekia/internal/parameters/getParameters" import "time" import "slices" import "errors" func GetEligibleHostsList(networkType byte)([][16]byte, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return nil, errors.New("GetEligibleHostsList called with invalid networkType: " + networkTypeString) } enabledHostsList, err := enabledHosts.GetEnabledHostsList(true, networkType) if (err != nil) { return nil, err } helpers.RandomizeListOrder(enabledHostsList) // We start by only removing hosts who are blocked unblockedHostsList := make([][16]byte, 0) for _, hostIdentityHash := range enabledHostsList{ hostIsBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(hostIdentityHash) if (err != nil) { return nil, err } if (hostIsBlocked == true){ continue } unblockedHostsList = append(unblockedHostsList, hostIdentityHash) } if (len(unblockedHostsList) < 10){ // We don't have enough hosts to be any more selective // We will never serve a blocked host, even if no hosts are available. // This is because a blocked host will always be manually blocked by the user return unblockedHostsList, nil } //Outputs: // -[]string: New list // -error fillHostsListWithLessDesireableHosts := func(inputList [][16]byte, lessDesireableHostsList [][16]byte)([][16]byte, error){ for _, hostIdentityHash := range lessDesireableHostsList{ alreadyAdded := slices.Contains(inputList, hostIdentityHash) if (alreadyAdded == true){ continue } inputList = append(inputList, hostIdentityHash) if (len(inputList) >= 10){ return inputList, nil } } // This is only reached if not enough hosts were available in the lessDesireableHostsList return nil, errors.New("fillHostsListWithLessDesireableHosts called with lessDesireableHostsList with insufficient hosts.") } nonmaliciousHostsList := make([][16]byte, 0) for _, hostIdentityHash := range unblockedHostsList{ hostIsMalicious, err := maliciousHosts.CheckIfHostIsMalicious(hostIdentityHash) if (err != nil) { return nil, err } if (hostIsMalicious == true){ continue } nonmaliciousHostsList = append(nonmaliciousHostsList, hostIdentityHash) } if (len(nonmaliciousHostsList) < 10){ // We don't have enough hosts to be any more selective resultList, err := fillHostsListWithLessDesireableHosts(nonmaliciousHostsList, unblockedHostsList) if (err != nil) { return nil, err } return resultList, nil } reachableHostsList := make([][16]byte, 0) for _, hostIdentityHash := range nonmaliciousHostsList{ hostIsUnreachableClearnet, err := unreachableHosts.CheckIfHostIsUnreachable(hostIdentityHash, networkType, "Clearnet") if (err != nil) { return nil, err } hostIsUnreachableTor, err := unreachableHosts.CheckIfHostIsUnreachable(hostIdentityHash, networkType, "Tor") if (err != nil) { return nil, err } if (hostIsUnreachableClearnet == true && hostIsUnreachableTor == true){ continue } reachableHostsList = append(reachableHostsList, hostIdentityHash) } if (len(reachableHostsList) < 10){ resultList, err := fillHostsListWithLessDesireableHosts(reachableHostsList, nonmaliciousHostsList) if (err != nil) { return nil, err } return resultList, nil } fundedHostsList := make([][16]byte, 0) for _, hostIdentityHash := range reachableHostsList{ statusIsKnown, identityIsFunded, err := fundedStatus.GetIdentityIsFundedStatus(hostIdentityHash, networkType) if (err != nil) { return nil, err } if (statusIsKnown == true && identityIsFunded == false){ continue } fundedHostsList = append(fundedHostsList, hostIdentityHash) } if (len(fundedHostsList) < 10){ resultList, err := fillHostsListWithLessDesireableHosts(fundedHostsList, reachableHostsList) if (err != nil) { return nil, err } return resultList, nil } viewableHostsList := make([][16]byte, 0) // Map structure: Host identity hash -> Newest viewable profile broadcast time hostBroadcastTimesMap := make(map[[16]byte]int64) for _, hostIdentityHash := range fundedHostsList{ // Now we check to see if identity is not banned, and has a profile that is viewable // If the consensus status is unknown, we will allow the user to request from the host // This is necessary, because otherwise new users and hosts would not be able to connect to any hosts, because // they would not know the sticky consensus status of any hosts //TODO: Check if host is a network entry host, in which case, they cannot be banned exists, _, _, _, profileBroadcastTime, _, err := viewableProfiles.GetNewestViewableUserProfile(hostIdentityHash, networkType, true, true, true) if (err != nil) { return nil, err } if (exists == false){ continue } hostBroadcastTimesMap[hostIdentityHash] = profileBroadcastTime viewableHostsList = append(viewableHostsList, hostIdentityHash) } if (len(viewableHostsList) < 10){ resultList, err := fillHostsListWithLessDesireableHosts(viewableHostsList, fundedHostsList) if (err != nil) { return nil, err } return resultList, nil } // Now we check for hosts whose profiles are active // If the user has not turned Seekia on and downloaded profiles since the host inactivity expiration duration, // all host profiles will be expired unexpiredHostsList := make([][16]byte, 0) for _, hostIdentityHash := range viewableHostsList{ hostProfileBroadcastTime, exists := hostBroadcastTimesMap[hostIdentityHash] if (exists == false){ return nil, errors.New("hostBroadcastTimesMap missing host identity hash.") } _, maximumExistenceDuration, err := getParameters.GetHostProfileMaximumExistenceDuration(networkType) if (err != nil) { return nil, err } currentTime := time.Now().Unix() profileAge := currentTime - hostProfileBroadcastTime if (profileAge > maximumExistenceDuration){ // Host's profile has expired continue } unexpiredHostsList = append(unexpiredHostsList, hostIdentityHash) } if (len(unexpiredHostsList) < 10){ resultList, err := fillHostsListWithLessDesireableHosts(unexpiredHostsList, viewableHostsList) if (err != nil) { return nil, err } return resultList, nil } return unexpiredHostsList, nil }