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  }