gitlab.com/jokerrs1/Sia@v1.3.2/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  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  
    14  	"github.com/NebulousLabs/Sia/modules"
    15  	"github.com/NebulousLabs/Sia/modules/renter/hostdb/hosttree"
    16  	"github.com/NebulousLabs/Sia/persist"
    17  	siasync "github.com/NebulousLabs/Sia/sync"
    18  	"github.com/NebulousLabs/Sia/types"
    19  )
    20  
    21  var (
    22  	errNilCS      = errors.New("cannot create hostdb with nil consensus set")
    23  	errNilGateway = errors.New("cannot create hostdb with nil gateway")
    24  )
    25  
    26  // The HostDB is a database of potential hosts. It assigns a weight to each
    27  // host based on their hosting parameters, and then can select hosts at random
    28  // for uploading files.
    29  type HostDB struct {
    30  	// dependencies
    31  	cs         modules.ConsensusSet
    32  	deps       modules.Dependencies
    33  	gateway    modules.Gateway
    34  	log        *persist.Logger
    35  	mu         sync.RWMutex
    36  	persistDir string
    37  	tg         siasync.ThreadGroup
    38  
    39  	// The hostTree is the root node of the tree that organizes hosts by
    40  	// weight. The tree is necessary for selecting weighted hosts at
    41  	// random.
    42  	hostTree *hosttree.HostTree
    43  
    44  	// the scanPool is a set of hosts that need to be scanned. There are a
    45  	// handful of goroutines constantly waiting on the channel for hosts to
    46  	// scan. The scan map is used to prevent duplicates from entering the scan
    47  	// pool.
    48  	scanList        []modules.HostDBEntry
    49  	scanMap         map[string]struct{}
    50  	scanWait        bool
    51  	scanningThreads int
    52  
    53  	blockHeight types.BlockHeight
    54  	lastChange  modules.ConsensusChangeID
    55  }
    56  
    57  // New returns a new HostDB.
    58  func New(g modules.Gateway, cs modules.ConsensusSet, persistDir string) (*HostDB, error) {
    59  	// Check for nil inputs.
    60  	if g == nil {
    61  		return nil, errNilGateway
    62  	}
    63  	if cs == nil {
    64  		return nil, errNilCS
    65  	}
    66  	// Create HostDB using production dependencies.
    67  	return newHostDB(g, cs, persistDir, &modules.ProductionDependencies{})
    68  }
    69  
    70  // newHostDB creates a HostDB using the provided dependencies. It loads the old
    71  // persistence data, spawns the HostDB's scanning threads, and subscribes it to
    72  // the consensusSet.
    73  func newHostDB(g modules.Gateway, cs modules.ConsensusSet, persistDir string, deps modules.Dependencies) (*HostDB, error) {
    74  	// Create the HostDB object.
    75  	hdb := &HostDB{
    76  		cs:         cs,
    77  		deps:       deps,
    78  		gateway:    g,
    79  		persistDir: persistDir,
    80  
    81  		scanMap: make(map[string]struct{}),
    82  	}
    83  
    84  	// Create the persist directory if it does not yet exist.
    85  	err := os.MkdirAll(persistDir, 0700)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	// Create the logger.
    91  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "hostdb.log"))
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	hdb.log = logger
    96  	hdb.tg.AfterStop(func() {
    97  		if err := hdb.log.Close(); err != nil {
    98  			// Resort to println as the logger is in an uncertain state.
    99  			fmt.Println("Failed to close the hostdb logger:", err)
   100  		}
   101  	})
   102  
   103  	// The host tree is used to manage hosts and query them at random.
   104  	hdb.hostTree = hosttree.New(hdb.calculateHostWeight)
   105  
   106  	// Load the prior persistence structures.
   107  	hdb.mu.Lock()
   108  	err = hdb.load()
   109  	hdb.mu.Unlock()
   110  	if err != nil && !os.IsNotExist(err) {
   111  		return nil, err
   112  	}
   113  	hdb.tg.AfterStop(func() {
   114  		hdb.mu.Lock()
   115  		err := hdb.saveSync()
   116  		hdb.mu.Unlock()
   117  		if err != nil {
   118  			hdb.log.Println("Unable to save the hostdb:", err)
   119  		}
   120  	})
   121  
   122  	// Loading is complete, establish the save loop.
   123  	go hdb.threadedSaveLoop()
   124  
   125  	// Don't perform the remaining startup in the presence of a quitAfterLoad
   126  	// disruption.
   127  	if hdb.deps.Disrupt("quitAfterLoad") {
   128  		return hdb, nil
   129  	}
   130  
   131  	// COMPATv1.1.0
   132  	//
   133  	// If the block height has loaded as zero, the most recent consensus change
   134  	// needs to be set to perform a full rescan. This will also help the hostdb
   135  	// to pick up any hosts that it has incorrectly dropped in the past.
   136  	hdb.mu.Lock()
   137  	if hdb.blockHeight == 0 {
   138  		hdb.lastChange = modules.ConsensusChangeBeginning
   139  	}
   140  	hdb.mu.Unlock()
   141  
   142  	err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan())
   143  	if err == modules.ErrInvalidConsensusChangeID {
   144  		// Subscribe again using the new ID. This will cause a triggered scan
   145  		// on all of the hosts, but that should be acceptable.
   146  		hdb.mu.Lock()
   147  		hdb.blockHeight = 0
   148  		hdb.lastChange = modules.ConsensusChangeBeginning
   149  		hdb.mu.Unlock()
   150  		err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan())
   151  	}
   152  	if err != nil {
   153  		return nil, errors.New("hostdb subscription failed: " + err.Error())
   154  	}
   155  	hdb.tg.OnStop(func() {
   156  		cs.Unsubscribe(hdb)
   157  	})
   158  
   159  	// Spawn the scan loop during production, but allow it to be disrupted
   160  	// during testing. Primary reason is so that we can fill the hostdb with
   161  	// fake hosts and not have them marked as offline as the scanloop operates.
   162  	if !hdb.deps.Disrupt("disableScanLoop") {
   163  		go hdb.threadedScan()
   164  	}
   165  
   166  	return hdb, nil
   167  }
   168  
   169  // ActiveHosts returns a list of hosts that are currently online, sorted by
   170  // weight.
   171  func (hdb *HostDB) ActiveHosts() (activeHosts []modules.HostDBEntry) {
   172  	allHosts := hdb.hostTree.All()
   173  	for _, entry := range allHosts {
   174  		if len(entry.ScanHistory) == 0 {
   175  			continue
   176  		}
   177  		if !entry.ScanHistory[len(entry.ScanHistory)-1].Success {
   178  			continue
   179  		}
   180  		if !entry.AcceptingContracts {
   181  			continue
   182  		}
   183  		activeHosts = append(activeHosts, entry)
   184  	}
   185  	return activeHosts
   186  }
   187  
   188  // AllHosts returns all of the hosts known to the hostdb, including the
   189  // inactive ones.
   190  func (hdb *HostDB) AllHosts() (allHosts []modules.HostDBEntry) {
   191  	return hdb.hostTree.All()
   192  }
   193  
   194  // AverageContractPrice returns the average price of a host.
   195  func (hdb *HostDB) AverageContractPrice() (totalPrice types.Currency) {
   196  	sampleSize := 32
   197  	hosts := hdb.hostTree.SelectRandom(sampleSize, nil)
   198  	if len(hosts) == 0 {
   199  		return totalPrice
   200  	}
   201  	for _, host := range hosts {
   202  		totalPrice = totalPrice.Add(host.ContractPrice)
   203  	}
   204  	return totalPrice.Div64(uint64(len(hosts)))
   205  }
   206  
   207  // Close closes the hostdb, terminating its scanning threads
   208  func (hdb *HostDB) Close() error {
   209  	return hdb.tg.Stop()
   210  }
   211  
   212  // Host returns the HostSettings associated with the specified NetAddress. If
   213  // no matching host is found, Host returns false.
   214  func (hdb *HostDB) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) {
   215  	host, exists := hdb.hostTree.Select(spk)
   216  	if !exists {
   217  		return host, exists
   218  	}
   219  	hdb.mu.RLock()
   220  	updateHostHistoricInteractions(&host, hdb.blockHeight)
   221  	hdb.mu.RUnlock()
   222  	return host, exists
   223  }
   224  
   225  // RandomHosts implements the HostDB interface's RandomHosts() method. It takes
   226  // a number of hosts to return, and a slice of netaddresses to ignore, and
   227  // returns a slice of entries.
   228  func (hdb *HostDB) RandomHosts(n int, excludeKeys []types.SiaPublicKey) []modules.HostDBEntry {
   229  	return hdb.hostTree.SelectRandom(n, excludeKeys)
   230  }