gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/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 "strings" 14 "sync" 15 "time" 16 17 "gitlab.com/NebulousLabs/errors" 18 "gitlab.com/NebulousLabs/siamux" 19 "gitlab.com/NebulousLabs/threadgroup" 20 21 "gitlab.com/SkynetLabs/skyd/build" 22 "gitlab.com/SkynetLabs/skyd/skymodules" 23 "gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb/hosttree" 24 "go.sia.tech/siad/modules" 25 "go.sia.tech/siad/persist" 26 "go.sia.tech/siad/types" 27 ) 28 29 var ( 30 // ErrInitialScanIncomplete is returned whenever an operation is not 31 // allowed to be executed before the initial host scan has finished. 32 ErrInitialScanIncomplete = errors.New("initial hostdb scan is not yet completed") 33 errNilCS = errors.New("cannot create hostdb with nil consensus set") 34 errNilGateway = errors.New("cannot create hostdb with nil gateway") 35 errNilTPool = errors.New("cannot create hostdb with nil transaction pool") 36 errNilSiaMux = errors.New("cannot create hostdb with nil siamux") 37 38 // errHostNotFoundInTree is returned when the host is not found in the 39 // hosttree 40 errHostNotFoundInTree = errors.New("host not found in hosttree") 41 42 // errHostDomainBlocked is returned when the host domain has been 43 // blocked 44 errHostDomainBlocked = errors.New("host domain is blocked") 45 ) 46 47 // blockedDomains manages a list of blocked domains 48 type blockedDomains struct { 49 domains map[string]struct{} 50 mu sync.Mutex 51 } 52 53 // newBlockedDomains initializes a new blockedDomains 54 func newBlockedDomains(domains []string) *blockedDomains { 55 domainMap := make(map[string]struct{}) 56 for _, domain := range domains { 57 domainMap[domain] = struct{}{} 58 } 59 return &blockedDomains{ 60 domains: domainMap, 61 } 62 } 63 64 // managedAddDomains adds domains to the map of blocked domains 65 func (bd *blockedDomains) managedAddDomains(domains []string) { 66 bd.mu.Lock() 67 defer bd.mu.Unlock() 68 for _, domain := range domains { 69 bd.domains[domain] = struct{}{} 70 } 71 } 72 73 // managedBlockedDomains returns a list of the blocked domains 74 func (bd *blockedDomains) managedBlockedDomains() []string { 75 bd.mu.Lock() 76 defer bd.mu.Unlock() 77 var domains []string 78 for domain := range bd.domains { 79 domains = append(domains, domain) 80 } 81 82 return domains 83 } 84 85 // managedRemoveDomains removes domains from the map of blocked domains 86 func (bd *blockedDomains) managedRemoveDomains(domains []string) { 87 bd.mu.Lock() 88 defer bd.mu.Unlock() 89 for _, domain := range domains { 90 delete(bd.domains, domain) 91 } 92 } 93 94 // managedIsBlocked checks to see if the domain is blocked 95 func (bd *blockedDomains) managedIsBlocked(addr modules.NetAddress) bool { 96 // Grab the hostname 97 hostname := addr.Host() 98 99 // Address without the proper host:port format can't be blocked. This is 100 // usually a testing address so we will just ignore it. 101 if hostname == "" { 102 return false 103 } 104 105 // Check the full hostname 106 bd.mu.Lock() 107 defer bd.mu.Unlock() 108 _, blocked := bd.domains[hostname] 109 110 // Return if blocked or if the hostname is localhost or if it is an ipv6 111 // address 112 isIPV6 := strings.Contains(hostname, ":") 113 if blocked || hostname == "localhost" || isIPV6 { 114 return blocked 115 } 116 117 // Check for subdomains being blocked by a root domain 118 // 119 // Split the hostname into elements 120 elements := strings.Split(hostname, ".") 121 if len(elements) <= 1 { 122 return blocked 123 } 124 125 // Check domains 126 // 127 // We want to stop at the second to last element so that the last domain 128 // we check is of the format domain.com. This is to protect the user 129 // from accidentally submitted `com`, or some other TLD, and blocking 130 // every host in the hostdb. 131 for i := 0; i < len(elements)-1; i++ { 132 domainToCheck := strings.Join(elements[i:], ".") 133 _, blocked := bd.domains[domainToCheck] 134 if blocked { 135 return true 136 } 137 } 138 return false 139 } 140 141 // contractInfo contains information about a contract relevant to the HostDB. 142 type contractInfo struct { 143 HostPublicKey types.SiaPublicKey 144 StoredData uint64 `json:"storeddata"` 145 } 146 147 // The HostDB is a database of potential hosts. It assigns a weight to each 148 // host based on their hosting parameters, and then can select hosts at random 149 // for uploading files. 150 type HostDB struct { 151 // dependencies 152 cs modules.ConsensusSet 153 staticDeps modules.Dependencies 154 staticGateway modules.Gateway 155 staticMux *siamux.SiaMux 156 staticTpool modules.TransactionPool 157 158 staticLog *persist.Logger 159 mu sync.RWMutex 160 staticAlerter *modules.GenericAlerter 161 persistDir string 162 tg threadgroup.ThreadGroup 163 164 // knownContracts are contracts which the HostDB was informed about by the 165 // Contractor. It contains infos about active contracts we have formed with 166 // hosts. The mapkey is a serialized SiaPublicKey. 167 knownContracts map[string]contractInfo 168 169 // priceTables is a map that contains the most recent price table per host. 170 priceTables map[string]modules.RPCPriceTable 171 172 // The hostdb gets initialized with an allowance that can be modified. The 173 // allowance is used to build a weightFunc that the hosttree depends on to 174 // determine the weight of a host. 175 allowance skymodules.Allowance 176 weightFunc hosttree.WeightFunc 177 178 // txnFees are the most recent fees used in the score estimation. It is 179 // used to determine if the transaction fees have changed enough to warrant 180 // rebuilding the hosttree with an updated weight function. 181 txnFees types.Currency 182 183 // The staticHostTree is the root node of the tree that organizes hosts by 184 // weight. The tree is necessary for selecting weighted hosts at random. 185 staticHostTree *hosttree.HostTree 186 187 // the scanPool is a set of hosts that need to be scanned. There are a 188 // handful of goroutines constantly waiting on the channel for hosts to 189 // scan. The scan map is used to prevent duplicates from entering the scan 190 // pool. 191 initialScanComplete bool 192 initialScanLatencies []time.Duration 193 disableIPViolationCheck bool 194 scanList []skymodules.HostDBEntry 195 scanMap map[string]struct{} 196 scanWait bool 197 scanningThreads int 198 synced bool 199 200 // staticFilteredTree is a hosttree that only contains the hosts that align 201 // with the filterMode. The filteredHosts are the hosts that are submitted 202 // with the filterMode to determine which host should be in the 203 // staticFilteredTree 204 staticFilteredTree *hosttree.HostTree 205 filteredHosts map[string]types.SiaPublicKey 206 filterMode skymodules.FilterMode 207 208 // staticBlockedDomains tracks blocked domains for the hostdb. 209 staticBlockedDomains *blockedDomains 210 211 blockHeight types.BlockHeight 212 lastChange modules.ConsensusChangeID 213 } 214 215 // Enforce that HostDB satisfies the skymodules.HostDB interface. 216 var _ skymodules.HostDB = (*HostDB)(nil) 217 218 // insert inserts the HostDBEntry into both hosttrees 219 func (hdb *HostDB) insert(host skymodules.HostDBEntry) error { 220 // Check if host is a blocked domain 221 if hdb.staticBlockedDomains.managedIsBlocked(host.NetAddress) { 222 return errHostDomainBlocked 223 } 224 225 // Insert host into hosttree 226 err := hdb.staticHostTree.Insert(host) 227 _, ok := hdb.filteredHosts[host.PublicKey.String()] 228 isWhitelist := hdb.filterMode == skymodules.HostDBActiveWhitelist 229 if isWhitelist == ok { 230 errF := hdb.staticFilteredTree.Insert(host) 231 if errF != nil && errF != hosttree.ErrHostExists { 232 err = errors.Compose(err, errF) 233 } 234 } 235 return err 236 } 237 238 // modify modifies the HostDBEntry in both hosttrees 239 func (hdb *HostDB) modify(host skymodules.HostDBEntry) error { 240 // Check if host is a blocked domain 241 if hdb.staticBlockedDomains.managedIsBlocked(host.NetAddress) { 242 // If host is blocked, remove from the hosttree 243 return errors.Compose(errHostDomainBlocked, hdb.remove(host.PublicKey)) 244 } 245 246 // Modify the host 247 err := hdb.staticHostTree.Modify(host) 248 _, ok := hdb.filteredHosts[host.PublicKey.String()] 249 isWhitelist := hdb.filterMode == skymodules.HostDBActiveWhitelist 250 if isWhitelist == ok { 251 err = errors.Compose(err, hdb.staticFilteredTree.Modify(host)) 252 } 253 return err 254 } 255 256 // remove removes the HostDBEntry from both hosttrees 257 func (hdb *HostDB) remove(pk types.SiaPublicKey) error { 258 err := hdb.staticHostTree.Remove(pk) 259 _, ok := hdb.filteredHosts[pk.String()] 260 isWhitelist := hdb.filterMode == skymodules.HostDBActiveWhitelist 261 if isWhitelist == ok { 262 errF := hdb.staticFilteredTree.Remove(pk) 263 if err == nil && errF == hosttree.ErrNoSuchHost { 264 return nil 265 } 266 err = errors.Compose(err, errF) 267 } 268 return err 269 } 270 271 // managedSetWeightFunction is a helper function that sets the weightFunc field 272 // of the hostdb and also updates the the weight function used by the hosttrees 273 // by rebuilding them. Apart from the constructor of the hostdb, this method 274 // should be used to update the weight function in the hostdb and hosttrees. 275 func (hdb *HostDB) managedSetWeightFunction(wf hosttree.WeightFunc) error { 276 // Set the weight function in the hostdb. 277 hdb.mu.Lock() 278 defer hdb.mu.Unlock() 279 hdb.weightFunc = wf 280 // Update the hosttree and also the filteredTree if they are not the same. 281 err := hdb.staticHostTree.SetWeightFunction(wf) 282 if hdb.staticFilteredTree != hdb.staticHostTree { 283 err = errors.Compose(err, hdb.staticFilteredTree.SetWeightFunction(wf)) 284 } 285 return err 286 } 287 288 // managedSynced returns true if the hostdb is synced with the consensusset. 289 func (hdb *HostDB) managedSynced() bool { 290 hdb.mu.RLock() 291 defer hdb.mu.RUnlock() 292 return hdb.synced 293 } 294 295 // updateContracts rebuilds the knownContracts of the HostDB using the provided 296 // contracts. 297 func (hdb *HostDB) updateContracts(contracts []skymodules.RenterContract) { 298 // Build a new set of known contracts. 299 knownContracts := make(map[string]contractInfo) 300 for _, contract := range contracts { 301 if n := len(contract.Transaction.FileContractRevisions); n != 1 { 302 build.Critical("contract's transaction should contain 1 revision but had ", n) 303 continue 304 } 305 knownContracts[contract.HostPublicKey.String()] = contractInfo{ 306 HostPublicKey: contract.HostPublicKey, 307 StoredData: contract.Transaction.FileContractRevisions[0].NewFileSize, 308 } 309 } 310 311 // Update the set of known contracts in the hostdb, log if the number of 312 // contracts has decreased. 313 if len(hdb.knownContracts) > len(knownContracts) { 314 hdb.staticLog.Printf("Hostdb is decreasing from %v known contracts to %v known contracts", len(hdb.knownContracts), len(knownContracts)) 315 } 316 hdb.knownContracts = knownContracts 317 318 // Save the hostdb to persist the update. 319 err := hdb.saveSync() 320 if err != nil { 321 hdb.staticLog.Println("Error saving set of known contracts:", err) 322 } 323 324 // Purge the pricetables for unknown hosts to ensure we don't leak memory. 325 for host := range hdb.priceTables { 326 _, exists := knownContracts[host] 327 if !exists { 328 delete(hdb.priceTables, host) 329 } 330 } 331 } 332 333 // hostdbBlockingStartup handles the blocking portion of NewCustomHostDB. 334 func hostdbBlockingStartup(g modules.Gateway, cs modules.ConsensusSet, tpool modules.TransactionPool, siamux *siamux.SiaMux, persistDir string, deps modules.Dependencies) (*HostDB, error) { 335 // Check for nil inputs. 336 if g == nil { 337 return nil, errNilGateway 338 } 339 if cs == nil { 340 return nil, errNilCS 341 } 342 if tpool == nil { 343 return nil, errNilTPool 344 } 345 if siamux == nil { 346 return nil, errNilSiaMux 347 } 348 349 // Create the HostDB object. 350 hdb := &HostDB{ 351 cs: cs, 352 staticDeps: deps, 353 staticGateway: g, 354 persistDir: persistDir, 355 staticMux: siamux, 356 staticTpool: tpool, 357 358 staticBlockedDomains: newBlockedDomains(nil), 359 filteredHosts: make(map[string]types.SiaPublicKey), 360 knownContracts: make(map[string]contractInfo), 361 priceTables: make(map[string]modules.RPCPriceTable), 362 scanMap: make(map[string]struct{}), 363 staticAlerter: skymodules.NewAlerter("hostdb"), 364 } 365 366 // Set the allowance, txnFees and hostweight function. 367 hdb.allowance = skymodules.DefaultAllowance 368 _, hdb.txnFees = hdb.staticTpool.FeeEstimation() 369 hdb.weightFunc = hdb.managedCalculateHostWeightFn(hdb.allowance) 370 371 // Create the persist directory if it does not yet exist. 372 err := os.MkdirAll(persistDir, 0700) 373 if err != nil { 374 return nil, err 375 } 376 377 // Create the logger. 378 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "hostdb.log")) 379 if err != nil { 380 return nil, err 381 } 382 hdb.staticLog = logger 383 err = hdb.tg.AfterStop(func() error { 384 if err := hdb.staticLog.Close(); err != nil { 385 // Resort to println as the logger is in an uncertain state. 386 fmt.Println("Failed to close the hostdb logger:", err) 387 return err 388 } 389 return nil 390 }) 391 if err != nil { 392 return nil, err 393 } 394 395 // The host tree is used to manage hosts and query them at random. The 396 // filteredTree is used when whitelist or blacklist is enabled 397 hdb.staticHostTree = hosttree.New(hdb.weightFunc, deps.Resolver()) 398 hdb.staticFilteredTree = hdb.staticHostTree 399 400 // Load the prior persistence structures. 401 hdb.mu.Lock() 402 err = hdb.load() 403 hdb.mu.Unlock() 404 if err != nil && !os.IsNotExist(err) { 405 return nil, err 406 } 407 err = hdb.tg.AfterStop(func() error { 408 hdb.mu.Lock() 409 err := hdb.saveSync() 410 hdb.mu.Unlock() 411 if err != nil { 412 hdb.staticLog.Println("Unable to save the hostdb:", err) 413 return err 414 } 415 return nil 416 }) 417 if err != nil { 418 return nil, err 419 } 420 421 // Loading is complete, establish the save loop. 422 go hdb.threadedSaveLoop() 423 424 // Don't perform the remaining startup in the presence of a quitAfterLoad 425 // disruption. 426 if hdb.staticDeps.Disrupt("quitAfterLoad") { 427 return hdb, nil 428 } 429 430 // COMPATv1.1.0 431 // 432 // If the block height has loaded as zero, the most recent consensus change 433 // needs to be set to perform a full rescan. This will also help the hostdb 434 // to pick up any hosts that it has incorrectly dropped in the past. 435 hdb.mu.Lock() 436 if hdb.blockHeight == 0 { 437 hdb.lastChange = modules.ConsensusChangeBeginning 438 hdb.blockHeight = 0 439 } 440 hdb.mu.Unlock() 441 442 // Spawn the scan loop during production, but allow it to be disrupted 443 // during testing. Primary reason is so that we can fill the hostdb with 444 // fake hosts and not have them marked as offline as the scanloop operates. 445 if !hdb.staticDeps.Disrupt("DisableScanLoop") { 446 go hdb.threadedScan() 447 } else { 448 hdb.initialScanComplete = true 449 } 450 err = hdb.tg.OnStop(func() error { 451 cs.Unsubscribe(hdb) 452 return nil 453 }) 454 if err != nil { 455 return nil, err 456 } 457 return hdb, nil 458 } 459 460 // hostdbAsyncStartup handles the async portion of NewCustomHostDB. 461 func hostdbAsyncStartup(hdb *HostDB, cs modules.ConsensusSet) error { 462 if hdb.staticDeps.Disrupt("BlockAsyncStartup") { 463 return nil 464 } 465 err := cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 466 if err != nil && strings.Contains(err.Error(), threadgroup.ErrStopped.Error()) { 467 return err 468 } 469 if errors.Contains(err, modules.ErrInvalidConsensusChangeID) { 470 // Subscribe again using the new ID. This will cause a triggered scan 471 // on all of the hosts, but that should be acceptable. 472 hdb.mu.Lock() 473 hdb.blockHeight = 0 474 hdb.lastChange = modules.ConsensusChangeBeginning 475 hdb.mu.Unlock() 476 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 477 } 478 if err != nil && strings.Contains(err.Error(), threadgroup.ErrStopped.Error()) { 479 return nil 480 } 481 if err != nil { 482 return err 483 } 484 return nil 485 } 486 487 // New returns a new HostDB. 488 func New(g modules.Gateway, cs modules.ConsensusSet, tpool modules.TransactionPool, siamux *siamux.SiaMux, persistDir string) (*HostDB, <-chan error) { 489 // Create HostDB using production dependencies. 490 return NewCustomHostDB(g, cs, tpool, siamux, persistDir, modules.ProdDependencies) 491 } 492 493 // NewCustomHostDB creates a HostDB using the provided dependencies. It loads the old 494 // persistence data, spawns the HostDB's scanning threads, and subscribes it to 495 // the consensusSet. 496 func NewCustomHostDB(g modules.Gateway, cs modules.ConsensusSet, tpool modules.TransactionPool, siamux *siamux.SiaMux, persistDir string, deps modules.Dependencies) (*HostDB, <-chan error) { 497 errChan := make(chan error, 1) 498 499 // Blocking startup. 500 hdb, err := hostdbBlockingStartup(g, cs, tpool, siamux, persistDir, deps) 501 if err != nil { 502 errChan <- err 503 return nil, errChan 504 } 505 // Parts of the blocking startup and the whole async startup should be 506 // skipped. 507 if hdb.staticDeps.Disrupt("quitAfterLoad") { 508 close(errChan) 509 return hdb, errChan 510 } 511 // non-blocking startup. 512 go func() { 513 defer close(errChan) 514 if err := hdb.tg.Add(); err != nil { 515 errChan <- err 516 return 517 } 518 defer hdb.tg.Done() 519 // Subscribe to the consensus set in a separate goroutine. 520 err := hostdbAsyncStartup(hdb, cs) 521 if err != nil { 522 errChan <- err 523 } 524 }() 525 return hdb, errChan 526 } 527 528 // ActiveHosts returns a list of hosts that are currently online, sorted by 529 // weight. If hostdb is in black or white list mode, then only active hosts from 530 // the filteredTree will be returned 531 func (hdb *HostDB) ActiveHosts() (activeHosts []skymodules.HostDBEntry, err error) { 532 if err = hdb.tg.Add(); err != nil { 533 return activeHosts, err 534 } 535 defer hdb.tg.Done() 536 537 hdb.mu.RLock() 538 allHosts := hdb.staticFilteredTree.All() 539 hdb.mu.RUnlock() 540 for _, entry := range allHosts { 541 if len(entry.ScanHistory) == 0 { 542 continue 543 } 544 if !entry.ScanHistory[len(entry.ScanHistory)-1].Success { 545 continue 546 } 547 if !entry.AcceptingContracts { 548 continue 549 } 550 activeHosts = append(activeHosts, entry) 551 } 552 return activeHosts, err 553 } 554 555 // AllHosts returns all of the hosts known to the hostdb, including the inactive 556 // ones. AllHosts is not filtered by blacklist or whitelist mode. 557 func (hdb *HostDB) AllHosts() (allHosts []skymodules.HostDBEntry, err error) { 558 if err := hdb.tg.Add(); err != nil { 559 return allHosts, err 560 } 561 defer hdb.tg.Done() 562 hdb.mu.RLock() 563 defer hdb.mu.RUnlock() 564 return hdb.staticHostTree.All(), nil 565 } 566 567 // BlockDomains blocks all hosts with matching domains 568 // 569 // NOTE: Blocking a host removes them from the hosttree entirely. This should 570 // only be used for malicious hosts that you never wish to form contracts with. 571 // If you don't want to completely remove a host, consider using the filtermode. 572 func (hdb *HostDB) BlockDomains(domains []string) error { 573 if err := hdb.tg.Add(); err != nil { 574 return err 575 } 576 defer hdb.tg.Done() 577 578 // Add the domains to the map of blocked domains 579 hdb.staticBlockedDomains.managedAddDomains(domains) 580 581 hdb.mu.Lock() 582 defer hdb.mu.Unlock() 583 584 // Get all the hosts 585 allHosts := hdb.staticHostTree.All() 586 587 // Remove any blocked hosts from the hosttree 588 for _, host := range allHosts { 589 if !hdb.staticBlockedDomains.managedIsBlocked(host.NetAddress) { 590 continue 591 } 592 err := hdb.remove(host.PublicKey) 593 if err != nil { 594 hdb.staticLog.Println("WARN: error removing host from hosttree", err) 595 } 596 } 597 598 // Save the updates 599 return hdb.saveSync() 600 } 601 602 // BlockedDomains returns a list of all the blocked domains 603 func (hdb *HostDB) BlockedDomains() ([]string, error) { 604 if err := hdb.tg.Add(); err != nil { 605 return nil, err 606 } 607 defer hdb.tg.Done() 608 609 return hdb.staticBlockedDomains.managedBlockedDomains(), nil 610 } 611 612 // CheckForIPViolations accepts a number of host public keys and returns the 613 // ones that violate the rules of the addressFilter. 614 func (hdb *HostDB) CheckForIPViolations(hosts []types.SiaPublicKey) ([]types.SiaPublicKey, error) { 615 if err := hdb.tg.Add(); err != nil { 616 return nil, err 617 } 618 defer hdb.tg.Done() 619 // If the check was disabled we don't return any bad hosts. 620 hdb.mu.RLock() 621 defer hdb.mu.RUnlock() 622 disabled := hdb.disableIPViolationCheck 623 if disabled { 624 return nil, nil 625 } 626 627 var entries []skymodules.HostDBEntry 628 var badHosts []types.SiaPublicKey 629 630 // Get the entries which correspond to the keys. 631 for _, host := range hosts { 632 entry, exists := hdb.staticHostTree.Select(host) 633 if !exists { 634 // A host that's not in the hostdb is bad. 635 badHosts = append(badHosts, host) 636 continue 637 } 638 entries = append(entries, entry) 639 } 640 641 // Sort the entries by the amount of time they have occupied their 642 // corresponding subnets. This is the order in which they will be passed 643 // into the filter which prioritizes entries which are passed in earlier. 644 // That means 'younger' entries will be replaced in case of a violation. 645 sort.Slice(entries, func(i, j int) bool { 646 return entries[i].LastIPNetChange.Before(entries[j].LastIPNetChange) 647 }) 648 649 // Create a filter and apply it. 650 filter := hosttree.NewFilter(hdb.staticDeps.Resolver()) 651 for _, entry := range entries { 652 // Check if the host violates the rules. 653 if filter.Filtered(entry.NetAddress) { 654 badHosts = append(badHosts, entry.PublicKey) 655 continue 656 } 657 // If it didn't then we add it to the filter. 658 filter.Add(entry.NetAddress) 659 } 660 return badHosts, nil 661 } 662 663 // Close closes the hostdb, terminating its scanning threads 664 func (hdb *HostDB) Close() error { 665 return hdb.tg.Stop() 666 } 667 668 // Host returns the HostSettings associated with the specified pubkey. If no 669 // matching host is found, Host returns false. For black and white list modes, 670 // the Filtered field for the HostDBEntry is set to indicate it the host is 671 // being filtered from the filtered hosttree 672 func (hdb *HostDB) Host(spk types.SiaPublicKey) (skymodules.HostDBEntry, bool, error) { 673 if err := hdb.tg.Add(); err != nil { 674 return skymodules.HostDBEntry{}, false, errors.AddContext(err, "error adding hostdb threadgroup:") 675 } 676 defer hdb.tg.Done() 677 678 hdb.mu.Lock() 679 whitelist := hdb.filterMode == skymodules.HostDBActiveWhitelist 680 filteredHosts := hdb.filteredHosts 681 hdb.mu.Unlock() 682 host, exists := hdb.staticHostTree.Select(spk) 683 if !exists { 684 return host, exists, errHostNotFoundInTree 685 } 686 _, ok := filteredHosts[spk.String()] 687 host.Filtered = whitelist != ok 688 hdb.mu.RLock() 689 updateHostHistoricInteractions(&host, hdb.blockHeight) 690 hdb.mu.RUnlock() 691 return host, exists, nil 692 } 693 694 // Filter returns the hostdb's filterMode and filteredHosts 695 func (hdb *HostDB) Filter() (skymodules.FilterMode, map[string]types.SiaPublicKey, error) { 696 if err := hdb.tg.Add(); err != nil { 697 return skymodules.HostDBFilterError, nil, errors.AddContext(err, "error adding hostdb threadgroup:") 698 } 699 defer hdb.tg.Done() 700 701 hdb.mu.RLock() 702 defer hdb.mu.RUnlock() 703 filteredHosts := make(map[string]types.SiaPublicKey) 704 for k, v := range hdb.filteredHosts { 705 filteredHosts[k] = v 706 } 707 return hdb.filterMode, filteredHosts, nil 708 } 709 710 // SetFilterMode sets the hostdb filter mode 711 func (hdb *HostDB) SetFilterMode(fm skymodules.FilterMode, hosts []types.SiaPublicKey) error { 712 if err := hdb.tg.Add(); err != nil { 713 return errors.AddContext(err, "error adding hostdb threadgroup:") 714 } 715 defer hdb.tg.Done() 716 hdb.mu.Lock() 717 defer hdb.mu.Unlock() 718 719 // Check for error 720 if fm == skymodules.HostDBFilterError { 721 return errors.New("Cannot set hostdb filter mode, provided filter mode is an error") 722 } 723 // Check if disabling 724 if fm == skymodules.HostDBDisableFilter { 725 // Reset filtered field for hosts 726 for _, pk := range hdb.filteredHosts { 727 err := hdb.staticHostTree.SetFiltered(pk, false) 728 if err != nil { 729 hdb.staticLog.Println("Unable to mark entry as not filtered:", err) 730 } 731 } 732 // Reset filtered fields 733 hdb.staticFilteredTree = hdb.staticHostTree 734 hdb.filteredHosts = make(map[string]types.SiaPublicKey) 735 hdb.filterMode = fm 736 return nil 737 } 738 739 // Check for no hosts submitted with whitelist enabled 740 isWhitelist := fm == skymodules.HostDBActiveWhitelist 741 if len(hosts) == 0 && isWhitelist { 742 return errors.New("cannot enable whitelist without hosts") 743 } 744 745 // Create filtered HostTree 746 hdb.staticFilteredTree = hosttree.New(hdb.weightFunc, modules.ProdDependencies.Resolver()) 747 748 // Create filteredHosts map 749 filteredHosts := make(map[string]types.SiaPublicKey) 750 for _, h := range hosts { 751 // Add host to filtered host map 752 if _, ok := filteredHosts[h.String()]; ok { 753 continue 754 } 755 filteredHosts[h.String()] = h 756 757 // Update host in unfiltered hosttree 758 err := hdb.staticHostTree.SetFiltered(h, true) 759 if err != nil { 760 hdb.staticLog.Println("Unable to mark entry as filtered:", err) 761 } 762 } 763 var allErrs error 764 allHosts := hdb.staticHostTree.All() 765 for _, host := range allHosts { 766 // Add hosts to filtered tree 767 _, ok := filteredHosts[host.PublicKey.String()] 768 if isWhitelist != ok { 769 continue 770 } 771 err := hdb.staticFilteredTree.Insert(host) 772 if err != nil { 773 allErrs = errors.Compose(allErrs, err) 774 } 775 } 776 hdb.filteredHosts = filteredHosts 777 hdb.filterMode = fm 778 return errors.Compose(allErrs, hdb.saveSync()) 779 } 780 781 // InitialScanComplete returns a boolean indicating if the initial scan of the 782 // hostdb is completed. 783 func (hdb *HostDB) InitialScanComplete() (complete bool, err error) { 784 if err = hdb.tg.Add(); err != nil { 785 return false, errors.AddContext(err, "error adding hostdb threadgroup:") 786 } 787 defer hdb.tg.Done() 788 hdb.mu.Lock() 789 defer hdb.mu.Unlock() 790 complete = hdb.initialScanComplete 791 return 792 } 793 794 // IPViolationsCheck returns a boolean indicating if the IP violation check is 795 // enabled or not. 796 func (hdb *HostDB) IPViolationsCheck() (bool, error) { 797 if err := hdb.tg.Add(); err != nil { 798 return false, errors.AddContext(err, "error adding hostdb threadgroup:") 799 } 800 defer hdb.tg.Done() 801 hdb.mu.RLock() 802 defer hdb.mu.RUnlock() 803 return !hdb.disableIPViolationCheck, nil 804 } 805 806 // PriceTable returns the most recent pricetable for the host that 807 // corresponds with given public key. If the pricetable was not found, nil 808 // is returned. 809 func (hdb *HostDB) PriceTable(pk types.SiaPublicKey) *modules.RPCPriceTable { 810 hdb.mu.RLock() 811 defer hdb.mu.RUnlock() 812 813 pt, exists := hdb.priceTables[pk.String()] 814 if !exists { 815 return nil 816 } 817 return &pt 818 } 819 820 // RegisterPriceTable will set the given price table as the most recent 821 // pricetable for the host that corresponds with the given public key. 822 func (hdb *HostDB) RegisterPriceTable(hpk types.SiaPublicKey, pt modules.RPCPriceTable) { 823 hdb.mu.Lock() 824 defer hdb.mu.Unlock() 825 hdb.priceTables[hpk.String()] = pt 826 } 827 828 // SetAllowance updates the allowance used by the hostdb for weighing hosts by 829 // updating the host weight function. It will completely rebuild the hosttree so 830 // it should be used with care. 831 func (hdb *HostDB) SetAllowance(allowance skymodules.Allowance) error { 832 if err := hdb.tg.Add(); err != nil { 833 return errors.AddContext(err, "error adding hostdb threadgroup:") 834 } 835 defer hdb.tg.Done() 836 837 // If the allowance is empty, set it to the default allowance. This ensures 838 // that the estimates are at least moderately grounded. 839 if reflect.DeepEqual(allowance, skymodules.Allowance{}) { 840 allowance = skymodules.DefaultAllowance 841 } 842 843 // Update the allowance. 844 hdb.mu.Lock() 845 hdb.allowance = allowance 846 hdb.mu.Unlock() 847 848 // Update the weight function. 849 wf := hdb.managedCalculateHostWeightFn(allowance) 850 return hdb.managedSetWeightFunction(wf) 851 } 852 853 // SetIPViolationCheck enables or disables the IP violation check. If disabled, 854 // CheckForIPViolations won't return bad hosts and RandomHosts will return the 855 // address blacklist. 856 func (hdb *HostDB) SetIPViolationCheck(enabled bool) error { 857 if err := hdb.tg.Add(); err != nil { 858 return errors.AddContext(err, "error adding hostdb threadgroup:") 859 } 860 defer hdb.tg.Done() 861 862 hdb.mu.Lock() 863 defer hdb.mu.Unlock() 864 hdb.disableIPViolationCheck = !enabled 865 return nil 866 } 867 868 // UnblockDomains removes domains from the blocked domains 869 // 870 // NOTE: This does not add hosts back to the hosttree that were previously 871 // blocked. 872 func (hdb *HostDB) UnblockDomains(domains []string) error { 873 if err := hdb.tg.Add(); err != nil { 874 return err 875 } 876 defer hdb.tg.Done() 877 878 // Add the domain to the map of blocked domains 879 hdb.staticBlockedDomains.managedRemoveDomains(domains) 880 881 // Save the updates 882 hdb.mu.Lock() 883 defer hdb.mu.Unlock() 884 return hdb.saveSync() 885 } 886 887 // UpdateContracts rebuilds the knownContracts of the HostBD using the provided 888 // contracts. 889 func (hdb *HostDB) UpdateContracts(contracts []skymodules.RenterContract) error { 890 if err := hdb.tg.Add(); err != nil { 891 return errors.AddContext(err, "error adding hostdb threadgroup:") 892 } 893 defer hdb.tg.Done() 894 hdb.mu.Lock() 895 defer hdb.mu.Unlock() 896 hdb.updateContracts(contracts) 897 return nil 898 }