seekia/internal/network/eligibleHosts/eligibleHosts.go

235 lines
7.7 KiB
Go

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