github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/hostdb/hosttree/hosttree.go (about)

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