gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/hostdb/hosttree/hosttree.go (about)

     1  package hosttree
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  
     7  	"gitlab.com/NebulousLabs/errors"
     8  	"gitlab.com/NebulousLabs/fastrand"
     9  
    10  	"gitlab.com/SkynetLabs/skyd/build"
    11  	"gitlab.com/SkynetLabs/skyd/skymodules"
    12  	"go.sia.tech/siad/modules"
    13  	"go.sia.tech/siad/types"
    14  )
    15  
    16  var (
    17  	// ErrHostExists is returned if an Insert is called with a public key that
    18  	// already exists in the tree.
    19  	ErrHostExists = errors.New("host already exists in the tree")
    20  
    21  	// ErrNoSuchHost is returned if Remove is called with a public key that does
    22  	// not exist in the tree.
    23  	ErrNoSuchHost = errors.New("no host with specified public key")
    24  )
    25  
    26  type (
    27  	// WeightFunc is a function used to weight a given HostDBEntry in the tree.
    28  	WeightFunc func(skymodules.HostDBEntry) ScoreBreakdown
    29  
    30  	// HostTree is used to store and select host database entries. Each HostTree
    31  	// is initialized with a weighting func that is able to assign a weight to
    32  	// each entry. The entries can then be selected at random, weighted by the
    33  	// weight func.
    34  	HostTree struct {
    35  		root *node
    36  
    37  		// hosts is a map of public keys to nodes.
    38  		hosts map[string]*node
    39  
    40  		// resolver is the Resolver that is used by the hosttree to resolve
    41  		// hostnames to IP addresses.
    42  		resolver modules.Resolver
    43  
    44  		// weightFn calculates the weight of a hostEntry
    45  		weightFn WeightFunc
    46  
    47  		mu sync.Mutex
    48  	}
    49  
    50  	// hostEntry is an entry in the host tree.
    51  	hostEntry struct {
    52  		skymodules.HostDBEntry
    53  		weight types.Currency
    54  	}
    55  
    56  	// node is a node in the tree.
    57  	node struct {
    58  		parent *node
    59  		left   *node
    60  		right  *node
    61  
    62  		count int  // cumulative count of this node and all children
    63  		taken bool // `taken` indicates whether there is an active host at this node or not.
    64  
    65  		weight types.Currency
    66  		entry  *hostEntry
    67  	}
    68  )
    69  
    70  // createNode creates a new node using the provided `parent` and `entry`.
    71  func createNode(parent *node, entry *hostEntry) *node {
    72  	return &node{
    73  		parent: parent,
    74  		weight: entry.weight,
    75  		count:  1,
    76  
    77  		taken: true,
    78  		entry: entry,
    79  	}
    80  }
    81  
    82  // New creates a new HostTree given a weight function and a resolver
    83  // for hostnames.
    84  func New(wf WeightFunc, resolver modules.Resolver) *HostTree {
    85  	return &HostTree{
    86  		hosts: make(map[string]*node),
    87  		root: &node{
    88  			count: 1,
    89  		},
    90  		resolver: resolver,
    91  		weightFn: wf,
    92  	}
    93  }
    94  
    95  // recursiveInsert inserts an entry into the appropriate place in the tree. The
    96  // running time of recursiveInsert is log(n) in the maximum number of elements
    97  // that have ever been in the tree.
    98  func (n *node) recursiveInsert(entry *hostEntry) (nodesAdded int, newnode *node) {
    99  	// If there is no parent and no children, and the node is not taken, assign
   100  	// this entry to this node.
   101  	if n.parent == nil && n.left == nil && n.right == nil && !n.taken {
   102  		n.entry = entry
   103  		n.taken = true
   104  		n.weight = entry.weight
   105  		newnode = n
   106  		return
   107  	}
   108  
   109  	n.weight = n.weight.Add(entry.weight)
   110  
   111  	// If the current node is empty, add the entry but don't increase the
   112  	// count.
   113  	if !n.taken {
   114  		n.taken = true
   115  		n.entry = entry
   116  		newnode = n
   117  		return
   118  	}
   119  
   120  	// Insert the element into the lest populated side.
   121  	if n.left == nil {
   122  		n.left = createNode(n, entry)
   123  		nodesAdded = 1
   124  		newnode = n.left
   125  	} else if n.right == nil {
   126  		n.right = createNode(n, entry)
   127  		nodesAdded = 1
   128  		newnode = n.right
   129  	} else if n.left.count <= n.right.count {
   130  		nodesAdded, newnode = n.left.recursiveInsert(entry)
   131  	} else {
   132  		nodesAdded, newnode = n.right.recursiveInsert(entry)
   133  	}
   134  
   135  	n.count += nodesAdded
   136  	return
   137  }
   138  
   139  // nodeAtWeight grabs an element in the tree that appears at the given weight.
   140  // Though the tree has an arbitrary sorting, a sufficiently random weight will
   141  // pull a random element. The tree is searched through in a post-ordered way.
   142  func (n *node) nodeAtWeight(weight types.Currency) *node {
   143  	// Sanity check - weight must be less than the total weight of the tree.
   144  	if weight.Cmp(n.weight) > 0 {
   145  		build.Critical("Node weight corruption")
   146  		return nil
   147  	}
   148  
   149  	// Check if the left or right child should be returned.
   150  	if n.left != nil {
   151  		if weight.Cmp(n.left.weight) < 0 {
   152  			return n.left.nodeAtWeight(weight)
   153  		}
   154  		weight = weight.Sub(n.left.weight) // Search from the 0th index of the right side.
   155  	}
   156  	if n.right != nil && weight.Cmp(n.right.weight) < 0 {
   157  		return n.right.nodeAtWeight(weight)
   158  	}
   159  
   160  	// Should we panic here instead?
   161  	if !n.taken {
   162  		build.Critical("Node tree structure corruption")
   163  		return nil
   164  	}
   165  
   166  	// Return the root entry.
   167  	return n
   168  }
   169  
   170  // remove takes a node and removes it from the tree by climbing through the
   171  // list of parents. remove does not delete nodes.
   172  func (n *node) remove() {
   173  	n.weight = n.weight.Sub(n.entry.weight)
   174  	n.taken = false
   175  	current := n.parent
   176  	for current != nil {
   177  		current.weight = current.weight.Sub(n.entry.weight)
   178  		current = current.parent
   179  	}
   180  }
   181  
   182  // Host returns the address of the HostEntry.
   183  func (he *hostEntry) Host() string {
   184  	return he.NetAddress.Host()
   185  }
   186  
   187  // All returns all of the hosts in the host tree, sorted by weight.
   188  func (ht *HostTree) All() []skymodules.HostDBEntry {
   189  	ht.mu.Lock()
   190  	defer ht.mu.Unlock()
   191  	return ht.all()
   192  }
   193  
   194  // Insert inserts the entry provided to `entry` into the host tree. Insert will
   195  // return an error if the input host already exists.
   196  func (ht *HostTree) Insert(hdbe skymodules.HostDBEntry) error {
   197  	ht.mu.Lock()
   198  	defer ht.mu.Unlock()
   199  	return ht.insert(hdbe)
   200  }
   201  
   202  // Remove removes the host with the public key provided by `pk`.
   203  func (ht *HostTree) Remove(pk types.SiaPublicKey) error {
   204  	ht.mu.Lock()
   205  	defer ht.mu.Unlock()
   206  
   207  	node, exists := ht.hosts[pk.String()]
   208  	if !exists {
   209  		return ErrNoSuchHost
   210  	}
   211  	node.remove()
   212  	delete(ht.hosts, pk.String())
   213  
   214  	return nil
   215  }
   216  
   217  // Modify updates a host entry at the given public key, replacing the old entry
   218  // with the entry provided by `newEntry`.
   219  func (ht *HostTree) Modify(hdbe skymodules.HostDBEntry) error {
   220  	ht.mu.Lock()
   221  	defer ht.mu.Unlock()
   222  
   223  	node, exists := ht.hosts[hdbe.PublicKey.String()]
   224  	if !exists {
   225  		return ErrNoSuchHost
   226  	}
   227  
   228  	node.remove()
   229  
   230  	entry := &hostEntry{
   231  		HostDBEntry: hdbe,
   232  		weight:      ht.weightFn(hdbe).Score(),
   233  	}
   234  
   235  	_, node = ht.root.recursiveInsert(entry)
   236  
   237  	ht.hosts[entry.PublicKey.String()] = node
   238  	return nil
   239  }
   240  
   241  // SetFiltered updates a host entry filtered field.
   242  func (ht *HostTree) SetFiltered(pubKey types.SiaPublicKey, filtered bool) error {
   243  	entry, ok := ht.Select(pubKey)
   244  	if !ok {
   245  		return ErrNoSuchHost
   246  	}
   247  	entry.Filtered = filtered
   248  	return ht.Modify(entry)
   249  }
   250  
   251  // SetWeightFunction resets the HostTree and assigns it a new weight
   252  // function. This resets the tree and reinserts all the hosts.
   253  func (ht *HostTree) SetWeightFunction(wf WeightFunc) error {
   254  	ht.mu.Lock()
   255  	defer ht.mu.Unlock()
   256  
   257  	// Get all the hosts.
   258  	allHosts := ht.all()
   259  
   260  	// Reset the tree
   261  	ht.hosts = make(map[string]*node)
   262  	ht.root = &node{
   263  		count: 1,
   264  	}
   265  
   266  	// Assign the new weight function.
   267  	ht.weightFn = wf
   268  
   269  	// Reinsert all the hosts. To prevent the host tree from having a
   270  	// catastrophic failure in the event of an error early on, we tally up all
   271  	// of the insertion errors and return them all at the end.
   272  	var insertErrs error
   273  	for _, hdbe := range allHosts {
   274  		if err := ht.insert(hdbe); err != nil {
   275  			insertErrs = errors.Compose(err, insertErrs)
   276  		}
   277  	}
   278  	return insertErrs
   279  }
   280  
   281  // Select returns the host with the provided public key, should the host exist.
   282  func (ht *HostTree) Select(spk types.SiaPublicKey) (skymodules.HostDBEntry, bool) {
   283  	ht.mu.Lock()
   284  	defer ht.mu.Unlock()
   285  
   286  	node, exists := ht.hosts[spk.String()]
   287  	if !exists {
   288  		return skymodules.HostDBEntry{}, false
   289  	}
   290  	return node.entry.HostDBEntry, true
   291  }
   292  
   293  // SelectRandom grabs a random n hosts from the tree. There will be no repeats,
   294  // but the length of the slice returned may be less than n, and may even be
   295  // zero.  The hosts that are returned first have the higher priority.
   296  //
   297  // Hosts passed to 'blacklist' will not be considered; pass `nil` if no
   298  // blacklist is desired. 'addressBlacklist' is similar to 'blacklist' but
   299  // instead of not considering the hosts in the list, hosts that use the same IP
   300  // subnet as those hosts will be ignored. In most cases those blacklists contain
   301  // the same elements but sometimes it is useful to block a host without blocking
   302  // its IP range.
   303  //
   304  // Hosts with a score of 1 will be ignored. 1 is the lowest score possible, at
   305  // which point it's impossible to distinguish between hosts. Any sane scoring
   306  // system should always have scores greater than 1 unless the host is
   307  // intentionally being given a low score to indicate that the host should not be
   308  // used.
   309  func (ht *HostTree) SelectRandom(n int, blacklist, addressBlacklist []types.SiaPublicKey) []skymodules.HostDBEntry {
   310  	return ht.SelectRandomWithWhitelist(n, blacklist, addressBlacklist, nil)
   311  }
   312  
   313  // SelectRandomWithWhitelist is the same as SelectRandom with the additional
   314  // whitelist parameter that guarantees that only whitelisted hosts can be
   315  // returned.
   316  func (ht *HostTree) SelectRandomWithWhitelist(n int, blacklist, addressBlacklist []types.SiaPublicKey, whitelist map[string]struct{}) []skymodules.HostDBEntry {
   317  	ht.mu.Lock()
   318  	defer ht.mu.Unlock()
   319  
   320  	var removedEntries []*hostEntry
   321  
   322  	// Create a filter.
   323  	filter := NewFilter(ht.resolver)
   324  
   325  	// Add the hosts from the addressBlacklist to the filter.
   326  	for _, pubkey := range addressBlacklist {
   327  		node, exists := ht.hosts[pubkey.String()]
   328  		if !exists {
   329  			continue
   330  		}
   331  		// Add the node to the addressFilter.
   332  		filter.Add(node.entry.NetAddress)
   333  	}
   334  	// Remove hosts we want to blacklist from the tree but remember them to make
   335  	// sure we can insert them later.
   336  	for _, pubkey := range blacklist {
   337  		node, exists := ht.hosts[pubkey.String()]
   338  		if !exists {
   339  			continue
   340  		}
   341  		// Remove the host from the tree.
   342  		node.remove()
   343  		delete(ht.hosts, pubkey.String())
   344  
   345  		// Remember the host to insert it again later.
   346  		removedEntries = append(removedEntries, node.entry)
   347  	}
   348  	// Remove hosts that are not on the whitelist but remember them to make sure
   349  	// we can insert them later.
   350  	if len(whitelist) > 0 {
   351  		for pubkey, node := range ht.hosts {
   352  			_, whitelisted := whitelist[pubkey]
   353  			if whitelisted {
   354  				continue
   355  			}
   356  			// Remove the host from the tree.
   357  			node.remove()
   358  			delete(ht.hosts, pubkey)
   359  
   360  			// Remember the host to insert it again later.
   361  			removedEntries = append(removedEntries, node.entry)
   362  		}
   363  	}
   364  
   365  	var hosts []skymodules.HostDBEntry
   366  
   367  	for len(hosts) < n && len(ht.hosts) > 0 {
   368  		randWeight := fastrand.BigIntn(ht.root.weight.Big())
   369  		node := ht.root.nodeAtWeight(types.NewCurrency(randWeight))
   370  		weightOne := types.NewCurrency64(1)
   371  
   372  		if node.entry.AcceptingContracts &&
   373  			len(node.entry.ScanHistory) > 0 &&
   374  			node.entry.ScanHistory[len(node.entry.ScanHistory)-1].Success &&
   375  			!filter.Filtered(node.entry.NetAddress) &&
   376  			node.entry.weight.Cmp(weightOne) > 0 {
   377  			// The host must be online and accepting contracts to be returned
   378  			// by the random function. It also has to pass the addressFilter
   379  			// check.
   380  			hosts = append(hosts, node.entry.HostDBEntry)
   381  
   382  			// If the host passed the filter, we add it to the filter.
   383  			filter.Add(node.entry.NetAddress)
   384  		}
   385  
   386  		removedEntries = append(removedEntries, node.entry)
   387  		node.remove()
   388  		delete(ht.hosts, node.entry.PublicKey.String())
   389  	}
   390  
   391  	for _, entry := range removedEntries {
   392  		_, node := ht.root.recursiveInsert(entry)
   393  		ht.hosts[entry.PublicKey.String()] = node
   394  	}
   395  
   396  	return hosts
   397  }
   398  
   399  // all returns all of the hosts in the host tree, sorted by weight.
   400  func (ht *HostTree) all() []skymodules.HostDBEntry {
   401  	he := make([]hostEntry, 0, len(ht.hosts))
   402  	for _, node := range ht.hosts {
   403  		he = append(he, *node.entry)
   404  	}
   405  	sort.Sort(byWeight(he))
   406  
   407  	entries := make([]skymodules.HostDBEntry, 0, len(he))
   408  	for _, entry := range he {
   409  		entries = append(entries, entry.HostDBEntry)
   410  	}
   411  	return entries
   412  }
   413  
   414  // insert inserts the entry provided to `entry` into the host tree. Insert will
   415  // return an error if the input host already exists.
   416  func (ht *HostTree) insert(hdbe skymodules.HostDBEntry) error {
   417  	entry := &hostEntry{
   418  		HostDBEntry: hdbe,
   419  		weight:      ht.weightFn(hdbe).Score(),
   420  	}
   421  
   422  	if _, exists := ht.hosts[entry.PublicKey.String()]; exists {
   423  		return ErrHostExists
   424  	}
   425  
   426  	_, node := ht.root.recursiveInsert(entry)
   427  
   428  	ht.hosts[entry.PublicKey.String()] = node
   429  	return nil
   430  }