github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/hostdb/scan.go (about) 1 package hostdb 2 3 // scan.go contains the functions which periodically scan the list of all hosts 4 // to see which hosts are online or offline, and to get any updates to the 5 // settings of the hosts. 6 7 import ( 8 "bytes" 9 "crypto/rand" 10 "math/big" 11 "time" 12 13 "github.com/NebulousLabs/Sia/build" 14 "github.com/NebulousLabs/Sia/crypto" 15 "github.com/NebulousLabs/Sia/encoding" 16 "github.com/NebulousLabs/Sia/modules" 17 "github.com/NebulousLabs/Sia/types" 18 ) 19 20 const ( 21 defaultScanSleep = 1*time.Hour + 37*time.Minute 22 maxScanSleep = 4 * time.Hour 23 minScanSleep = 1 * time.Hour 24 25 maxActiveHosts = 500 26 inactiveHostCheckupQuantity = 250 27 28 maxSettingsLen = 2e3 29 30 hostRequestTimeout = 5 * time.Second 31 32 // scanningThreads is the number of threads that will be probing hosts for 33 // their settings and checking for reliability. 34 scanningThreads = 25 35 ) 36 37 // Reliability is a measure of a host's uptime. 38 var ( 39 MaxReliability = types.NewCurrency64(225) // Given the scanning defaults, about 3 weeks of survival. 40 DefaultReliability = types.NewCurrency64(75) // Given the scanning defaults, about 1 week of survival. 41 UnreachablePenalty = types.NewCurrency64(1) 42 ) 43 44 // addHostToScanPool creates a gofunc that adds a host to the scan pool. If the 45 // scan pool is currently full, the blocking gofunc will not cause a deadlock. 46 // The gofunc is created inside of this function to eliminate the burden of 47 // needing to remember to call 'go addHostToScanPool'. 48 func (hdb *HostDB) scanHostEntry(entry *hostEntry) { 49 go func() { 50 hdb.scanPool <- entry 51 }() 52 } 53 54 // decrementReliability reduces the reliability of a node, moving it out of the 55 // set of active hosts or deleting it entirely if necessary. 56 func (hdb *HostDB) decrementReliability(addr modules.NetAddress, penalty types.Currency) { 57 // Look up the entry and decrement the reliability. 58 entry, exists := hdb.allHosts[addr] 59 if !exists { 60 // TODO: should panic here 61 return 62 } 63 entry.Reliability = entry.Reliability.Sub(penalty) 64 entry.Online = false 65 66 // If the entry is in the active database, remove it from the active 67 // database. 68 node, exists := hdb.activeHosts[addr] 69 if exists { 70 delete(hdb.activeHosts, entry.NetAddress) 71 node.removeNode() 72 } 73 74 // If the reliability has fallen to 0, remove the host from the 75 // database entirely. 76 if entry.Reliability.IsZero() { 77 delete(hdb.allHosts, addr) 78 } 79 } 80 81 // threadedProbeHosts tries to fetch the settings of a host. If successful, the 82 // host is put in the set of active hosts. If unsuccessful, the host id deleted 83 // from the set of active hosts. 84 func (hdb *HostDB) threadedProbeHosts() { 85 defer hdb.threadGroup.Done() 86 for hostEntry := range hdb.scanPool { 87 // Request settings from the queued host entry. 88 // TODO: use dialer.Cancel to shutdown quickly 89 hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey) 90 var settings modules.HostExternalSettings 91 err := func() error { 92 conn, err := hdb.dialer.DialTimeout(hostEntry.NetAddress, hostRequestTimeout) 93 if err != nil { 94 return err 95 } 96 defer conn.Close() 97 err = encoding.WriteObject(conn, modules.RPCSettings) 98 if err != nil { 99 return err 100 } 101 var pubkey crypto.PublicKey 102 copy(pubkey[:], hostEntry.PublicKey.Key) 103 return crypto.ReadSignedObject(conn, &settings, maxSettingsLen, pubkey) 104 }() 105 if err != nil { 106 hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "failed", err) 107 } else { 108 hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "succeeded") 109 } 110 111 // Now that network communication is done, lock the hostdb to modify the 112 // host entry. 113 func() { 114 hdb.mu.Lock() 115 defer hdb.mu.Unlock() 116 117 // Regardless of whether the host responded, add it to allHosts. 118 priorHost, exists := hdb.allHosts[hostEntry.NetAddress] 119 if !exists { 120 hdb.allHosts[hostEntry.NetAddress] = hostEntry 121 } 122 123 // If the scan was unsuccessful, decrement the host's reliability. 124 if err != nil { 125 if exists && bytes.Equal(priorHost.PublicKey.Key, hostEntry.PublicKey.Key) { 126 // Only decrement the reliability if the public key in the 127 // hostdb matches the public key in the host announcement - 128 // the failure may just be a failed signature, indicating 129 // the wrong public key. 130 hdb.decrementReliability(hostEntry.NetAddress, UnreachablePenalty) 131 } 132 return 133 } 134 135 // Update the host settings, reliability, and weight. The old NetAddress 136 // must be preserved. 137 settings.NetAddress = hostEntry.HostExternalSettings.NetAddress 138 hostEntry.HostExternalSettings = settings 139 hostEntry.Reliability = MaxReliability 140 hostEntry.Weight = calculateHostWeight(*hostEntry) 141 hostEntry.Online = true 142 143 // If 'maxActiveHosts' has not been reached, add the host to the 144 // activeHosts tree. 145 if _, exists := hdb.activeHosts[hostEntry.NetAddress]; !exists && len(hdb.activeHosts) < maxActiveHosts { 146 hdb.insertNode(hostEntry) 147 } 148 hdb.save() 149 }() 150 } 151 } 152 153 // threadedScan is an ongoing function which will query the full set of hosts 154 // every few hours to see who is online and available for uploading. 155 func (hdb *HostDB) threadedScan() { 156 defer hdb.threadGroup.Done() 157 for { 158 // Determine who to scan. At most 'maxActiveHosts' will be scanned, 159 // starting with the active hosts followed by a random selection of the 160 // inactive hosts. 161 func() { 162 hdb.mu.Lock() 163 defer hdb.mu.Unlock() 164 165 // Scan all active hosts. 166 for _, host := range hdb.activeHosts { 167 hdb.scanHostEntry(host.hostEntry) 168 } 169 170 // Assemble all of the inactive hosts into a single array. 171 var entries []*hostEntry 172 for _, entry := range hdb.allHosts { 173 _, exists := hdb.activeHosts[entry.NetAddress] 174 if !exists { 175 entries = append(entries, entry) 176 } 177 } 178 179 // Generate a random ordering of up to inactiveHostCheckupQuantity 180 // hosts. 181 hostOrder, err := crypto.Perm(len(entries)) 182 if err != nil { 183 hdb.log.Println("ERR: could not generate random permutation:", err) 184 } 185 186 // Scan each host. 187 for i := 0; i < len(hostOrder) && i < inactiveHostCheckupQuantity; i++ { 188 hdb.scanHostEntry(entries[hostOrder[i]]) 189 } 190 }() 191 192 // Sleep for a random amount of time before doing another round of 193 // scanning. The minimums and maximums keep the scan time reasonable, 194 // while the randomness prevents the scanning from always happening at 195 // the same time of day or week. 196 maxBig := big.NewInt(int64(maxScanSleep)) 197 minBig := big.NewInt(int64(minScanSleep)) 198 randSleep, err := rand.Int(rand.Reader, maxBig.Sub(maxBig, minBig)) 199 if err != nil { 200 build.Critical(err) 201 // If there's an error, sleep for the default amount of time. 202 defaultBig := big.NewInt(int64(defaultScanSleep)) 203 randSleep = defaultBig.Sub(defaultBig, minBig) 204 } 205 206 select { 207 // awaken and exit if hostdb is closing 208 case <-hdb.closeChan: 209 return 210 case <-time.After(time.Duration(randSleep.Int64()) + minScanSleep): 211 } 212 } 213 }