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 }