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 }