github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/hostdb/hostdb.go (about) 1 // Package hostdb provides a HostDB object that implements the renter.hostDB 2 // interface. The blockchain is scanned for host announcements and hosts that 3 // are found get added to the host database. The database continually scans the 4 // set of hosts it has found and updates who is online. 5 package hostdb 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "sort" 13 "sync" 14 "time" 15 16 "SiaPrime/modules" 17 "SiaPrime/modules/renter/hostdb/hosttree" 18 "SiaPrime/persist" 19 "SiaPrime/types" 20 "gitlab.com/NebulousLabs/threadgroup" 21 22 "gitlab.com/NebulousLabs/errors" 23 ) 24 25 var ( 26 // ErrInitialScanIncomplete is returned whenever an operation is not 27 // allowed to be executed before the initial host scan has finished. 28 ErrInitialScanIncomplete = errors.New("initial hostdb scan is not yet completed") 29 errNilCS = errors.New("cannot create hostdb with nil consensus set") 30 errNilGateway = errors.New("cannot create hostdb with nil gateway") 31 ) 32 33 // The HostDB is a database of potential hosts. It assigns a weight to each 34 // host based on their hosting parameters, and then can select hosts at random 35 // for uploading files. 36 type HostDB struct { 37 // dependencies 38 cs modules.ConsensusSet 39 deps modules.Dependencies 40 gateway modules.Gateway 41 log *persist.Logger 42 mu sync.RWMutex 43 persistDir string 44 tg threadgroup.ThreadGroup 45 46 // The hostdb gets initialized with an allowance that can be modified. The 47 // allowance is used to build a weightFunc that the hosttree depends on to 48 // determine the weight of a host. 49 allowance modules.Allowance 50 weightFunc hosttree.WeightFunc 51 52 // The hostTree is the root node of the tree that organizes hosts by 53 // weight. The tree is necessary for selecting weighted hosts at 54 // random. 55 hostTree *hosttree.HostTree 56 57 // the scanPool is a set of hosts that need to be scanned. There are a 58 // handful of goroutines constantly waiting on the channel for hosts to 59 // scan. The scan map is used to prevent duplicates from entering the scan 60 // pool. 61 initialScanComplete bool 62 initialScanLatencies []time.Duration 63 scanList []modules.HostDBEntry 64 scanMap map[string]struct{} 65 scanWait bool 66 scanningThreads int 67 68 blockHeight types.BlockHeight 69 lastChange modules.ConsensusChangeID 70 } 71 72 // New returns a new HostDB. 73 func New(g modules.Gateway, cs modules.ConsensusSet, persistDir string) (*HostDB, error) { 74 // Check for nil inputs. 75 if g == nil { 76 return nil, errNilGateway 77 } 78 if cs == nil { 79 return nil, errNilCS 80 } 81 // Create HostDB using production dependencies. 82 return NewCustomHostDB(g, cs, persistDir, modules.ProdDependencies) 83 } 84 85 // NewCustomHostDB creates a HostDB using the provided dependencies. It loads the old 86 // persistence data, spawns the HostDB's scanning threads, and subscribes it to 87 // the consensusSet. 88 func NewCustomHostDB(g modules.Gateway, cs modules.ConsensusSet, persistDir string, deps modules.Dependencies) (*HostDB, error) { 89 // Create the HostDB object. 90 hdb := &HostDB{ 91 cs: cs, 92 deps: deps, 93 gateway: g, 94 persistDir: persistDir, 95 96 scanMap: make(map[string]struct{}), 97 } 98 99 // Set the hostweight function. 100 hdb.allowance = modules.DefaultAllowance 101 hdb.weightFunc = hdb.calculateHostWeightFn(hdb.allowance) 102 103 // Create the persist directory if it does not yet exist. 104 err := os.MkdirAll(persistDir, 0700) 105 if err != nil { 106 return nil, err 107 } 108 109 // Create the logger. 110 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "hostdb.log")) 111 if err != nil { 112 return nil, err 113 } 114 hdb.log = logger 115 err = hdb.tg.AfterStop(func() error { 116 if err := hdb.log.Close(); err != nil { 117 // Resort to println as the logger is in an uncertain state. 118 fmt.Println("Failed to close the hostdb logger:", err) 119 return err 120 } 121 return nil 122 }) 123 if err != nil { 124 return nil, err 125 } 126 127 // The host tree is used to manage hosts and query them at random. 128 hdb.hostTree = hosttree.New(hdb.weightFunc, deps.Resolver()) 129 130 // Load the prior persistence structures. 131 hdb.mu.Lock() 132 err = hdb.load() 133 hdb.mu.Unlock() 134 if err != nil && !os.IsNotExist(err) { 135 return nil, err 136 } 137 err = hdb.tg.AfterStop(func() error { 138 hdb.mu.Lock() 139 err := hdb.saveSync() 140 hdb.mu.Unlock() 141 if err != nil { 142 hdb.log.Println("Unable to save the hostdb:", err) 143 return err 144 } 145 return nil 146 }) 147 if err != nil { 148 return nil, err 149 } 150 151 // Loading is complete, establish the save loop. 152 go hdb.threadedSaveLoop() 153 154 // Don't perform the remaining startup in the presence of a quitAfterLoad 155 // disruption. 156 if hdb.deps.Disrupt("quitAfterLoad") { 157 return hdb, nil 158 } 159 160 // COMPATv1.1.0 161 // 162 // If the block height has loaded as zero, the most recent consensus change 163 // needs to be set to perform a full rescan. This will also help the hostdb 164 // to pick up any hosts that it has incorrectly dropped in the past. 165 hdb.mu.Lock() 166 if hdb.blockHeight == 0 { 167 hdb.lastChange = modules.ConsensusChangeBeginning 168 } 169 hdb.mu.Unlock() 170 171 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 172 if err == modules.ErrInvalidConsensusChangeID { 173 // Subscribe again using the new ID. This will cause a triggered scan 174 // on all of the hosts, but that should be acceptable. 175 hdb.mu.Lock() 176 hdb.blockHeight = 0 177 hdb.lastChange = modules.ConsensusChangeBeginning 178 hdb.mu.Unlock() 179 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 180 } 181 if err != nil { 182 return nil, errors.New("hostdb subscription failed: " + err.Error()) 183 } 184 err = hdb.tg.OnStop(func() error { 185 cs.Unsubscribe(hdb) 186 return nil 187 }) 188 if err != nil { 189 return nil, err 190 } 191 192 // Spawn the scan loop during production, but allow it to be disrupted 193 // during testing. Primary reason is so that we can fill the hostdb with 194 // fake hosts and not have them marked as offline as the scanloop operates. 195 if !hdb.deps.Disrupt("disableScanLoop") { 196 go hdb.threadedScan() 197 } else { 198 hdb.initialScanComplete = true 199 } 200 201 return hdb, nil 202 } 203 204 // ActiveHosts returns a list of hosts that are currently online, sorted by 205 // weight. 206 func (hdb *HostDB) ActiveHosts() (activeHosts []modules.HostDBEntry) { 207 allHosts := hdb.hostTree.All() 208 for _, entry := range allHosts { 209 if len(entry.ScanHistory) == 0 { 210 continue 211 } 212 if !entry.ScanHistory[len(entry.ScanHistory)-1].Success { 213 continue 214 } 215 if !entry.AcceptingContracts { 216 continue 217 } 218 activeHosts = append(activeHosts, entry) 219 } 220 return activeHosts 221 } 222 223 // AllHosts returns all of the hosts known to the hostdb, including the 224 // inactive ones. 225 func (hdb *HostDB) AllHosts() (allHosts []modules.HostDBEntry) { 226 return hdb.hostTree.All() 227 } 228 229 // AverageContractPrice returns the average price of a host. 230 func (hdb *HostDB) AverageContractPrice() (totalPrice types.Currency) { 231 sampleSize := 32 232 hosts := hdb.hostTree.SelectRandom(sampleSize, nil, nil) 233 if len(hosts) == 0 { 234 return totalPrice 235 } 236 for _, host := range hosts { 237 totalPrice = totalPrice.Add(host.ContractPrice) 238 } 239 return totalPrice.Div64(uint64(len(hosts))) 240 } 241 242 // CheckForIPViolations accepts a number of host public keys and returns the 243 // ones that violate the rules of the addressFilter. 244 func (hdb *HostDB) CheckForIPViolations(hosts []types.SiaPublicKey) []types.SiaPublicKey { 245 var entries []modules.HostDBEntry 246 var badHosts []types.SiaPublicKey 247 248 // Get the entries which correspond to the keys. 249 for _, host := range hosts { 250 entry, exists := hdb.hostTree.Select(host) 251 if !exists { 252 // A host that's not in the hostdb is bad. 253 badHosts = append(badHosts, host) 254 continue 255 } 256 entries = append(entries, entry) 257 } 258 259 // Sort the entries by the amount of time they have occupied their 260 // corresponding subnets. This is the order in which they will be passed 261 // into the filter which prioritizes entries which are passed in earlier. 262 // That means 'younger' entries will be replaced in case of a violation. 263 sort.Slice(entries, func(i, j int) bool { 264 return entries[i].LastIPNetChange.Before(entries[j].LastIPNetChange) 265 }) 266 267 // Create a filter and apply it. 268 filter := hosttree.NewFilter(hdb.deps.Resolver()) 269 for _, entry := range entries { 270 // Check if the host violates the rules. 271 if filter.Filtered(entry.NetAddress) { 272 badHosts = append(badHosts, entry.PublicKey) 273 continue 274 } 275 // If it didn't then we add it to the filter. 276 filter.Add(entry.NetAddress) 277 } 278 return badHosts 279 } 280 281 // Close closes the hostdb, terminating its scanning threads 282 func (hdb *HostDB) Close() error { 283 return hdb.tg.Stop() 284 } 285 286 // Host returns the HostSettings associated with the specified NetAddress. If 287 // no matching host is found, Host returns false. 288 func (hdb *HostDB) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) { 289 host, exists := hdb.hostTree.Select(spk) 290 if !exists { 291 return host, exists 292 } 293 hdb.mu.RLock() 294 updateHostHistoricInteractions(&host, hdb.blockHeight) 295 hdb.mu.RUnlock() 296 return host, exists 297 } 298 299 // InitialScanComplete returns a boolean indicating if the initial scan of the 300 // hostdb is completed. 301 func (hdb *HostDB) InitialScanComplete() (complete bool, err error) { 302 if err = hdb.tg.Add(); err != nil { 303 return 304 } 305 defer hdb.tg.Done() 306 hdb.mu.Lock() 307 defer hdb.mu.Unlock() 308 complete = hdb.initialScanComplete 309 return 310 } 311 312 // RandomHosts implements the HostDB interface's RandomHosts() method. It takes 313 // a number of hosts to return, and a slice of netaddresses to ignore, and 314 // returns a slice of entries. 315 func (hdb *HostDB) RandomHosts(n int, blacklist, addressBlacklist []types.SiaPublicKey) ([]modules.HostDBEntry, error) { 316 hdb.mu.RLock() 317 initialScanComplete := hdb.initialScanComplete 318 hdb.mu.RUnlock() 319 if !initialScanComplete { 320 return []modules.HostDBEntry{}, ErrInitialScanIncomplete 321 } 322 return hdb.hostTree.SelectRandom(n, blacklist, addressBlacklist), nil 323 } 324 325 // RandomHostsWithAllowance works as RandomHosts but uses a temporary hosttree 326 // created from the specified allowance. This is a very expensive call and 327 // should be used with caution. 328 func (hdb *HostDB) RandomHostsWithAllowance(n int, blacklist, addressBlacklist []types.SiaPublicKey, allowance modules.Allowance) ([]modules.HostDBEntry, error) { 329 hdb.mu.RLock() 330 initialScanComplete := hdb.initialScanComplete 331 hdb.mu.RUnlock() 332 if !initialScanComplete { 333 return []modules.HostDBEntry{}, ErrInitialScanIncomplete 334 } 335 // Create a temporary hosttree from the given allowance. 336 ht := hosttree.New(hdb.calculateHostWeightFn(allowance), hdb.deps.Resolver()) 337 338 // Insert all known hosts. 339 var insertErrs error 340 allHosts := hdb.hostTree.All() 341 for _, host := range allHosts { 342 if err := ht.Insert(host); err != nil { 343 insertErrs = errors.Compose(insertErrs, err) 344 } 345 } 346 347 // Select hosts from the temporary hosttree. 348 return ht.SelectRandom(n, blacklist, addressBlacklist), insertErrs 349 } 350 351 // SetAllowance updates the allowance used by the hostdb for weighing hosts by 352 // updating the host weight function. It will completely rebuild the hosttree so 353 // it should be used with care. 354 func (hdb *HostDB) SetAllowance(allowance modules.Allowance) error { 355 // If the allowance is empty, set it to the default allowance. This ensures 356 // that the estimates are at least moderately grounded. 357 if reflect.DeepEqual(allowance, modules.Allowance{}) { 358 allowance = modules.DefaultAllowance 359 } 360 361 // Update the weight function. 362 hdb.mu.Lock() 363 hdb.allowance = allowance 364 hdb.weightFunc = hdb.calculateHostWeightFn(allowance) 365 hdb.mu.Unlock() 366 367 // Update the trees weight function. 368 return hdb.hostTree.SetWeightFunction(hdb.calculateHostWeightFn(allowance)) 369 }