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  }