github.com/nebulouslabs/sia@v1.3.7/modules/renter/hostdb/hosttree/hosttree.go (about) 1 package hosttree 2 3 import ( 4 "errors" 5 "sort" 6 "sync" 7 8 "github.com/NebulousLabs/Sia/build" 9 "github.com/NebulousLabs/Sia/modules" 10 "github.com/NebulousLabs/Sia/types" 11 "github.com/NebulousLabs/fastrand" 12 ) 13 14 var ( 15 // errHostExists is returned if an Insert is called with a public key that 16 // already exists in the tree. 17 errHostExists = errors.New("host already exists in the tree") 18 19 // errNegativeWeight is returned from an Insert() call if an entry with a 20 // negative weight is added to the tree. Entries must always have a positive 21 // weight. 22 errNegativeWeight = errors.New("cannot insert using a negative weight") 23 24 // errNilEntry is returned if a fetch call results in a nil tree entry. nodes 25 // should always have a non-nil entry, unless they have been Delete()ed. 26 errNilEntry = errors.New("node has a nil entry") 27 28 // errNoSuchHost is returned if Remove is called with a public key that does 29 // not exist in the tree. 30 errNoSuchHost = errors.New("no host with specified public key") 31 32 // errWeightTooHeavy is returned from a SelectRandom() call if a weight that exceeds 33 // the total weight of the tree is requested. 34 errWeightTooHeavy = errors.New("requested a too-heavy weight") 35 ) 36 37 type ( 38 // WeightFunc is a function used to weight a given HostDBEntry in the tree. 39 WeightFunc func(modules.HostDBEntry) types.Currency 40 41 // HostTree is used to store and select host database entries. Each HostTree 42 // is initialized with a weighting func that is able to assign a weight to 43 // each entry. The entries can then be selected at random, weighted by the 44 // weight func. 45 HostTree struct { 46 root *node 47 48 // hosts is a map of public keys to nodes. 49 hosts map[string]*node 50 51 // weightFn calculates the weight of a hostEntry 52 weightFn WeightFunc 53 54 mu sync.Mutex 55 } 56 57 // hostEntry is an entry in the host tree. 58 hostEntry struct { 59 modules.HostDBEntry 60 weight types.Currency 61 } 62 63 // node is a node in the tree. 64 node struct { 65 parent *node 66 left *node 67 right *node 68 69 count int // cumulative count of this node and all children 70 taken bool // `taken` indicates whether there is an active host at this node or not. 71 72 weight types.Currency 73 entry *hostEntry 74 } 75 ) 76 77 // createNode creates a new node using the provided `parent` and `entry`. 78 func createNode(parent *node, entry *hostEntry) *node { 79 return &node{ 80 parent: parent, 81 weight: entry.weight, 82 count: 1, 83 84 taken: true, 85 entry: entry, 86 } 87 } 88 89 // New creates a new, empty, HostTree. It takes one argument, a `WeightFunc`, 90 // which is used to determine the weight of a node on Insert. 91 func New(wf WeightFunc) *HostTree { 92 return &HostTree{ 93 root: &node{ 94 count: 1, 95 }, 96 weightFn: wf, 97 hosts: make(map[string]*node), 98 } 99 } 100 101 // recursiveInsert inserts an entry into the appropriate place in the tree. The 102 // running time of recursiveInsert is log(n) in the maximum number of elements 103 // that have ever been in the tree. 104 func (n *node) recursiveInsert(entry *hostEntry) (nodesAdded int, newnode *node) { 105 // If there is no parent and no children, and the node is not taken, assign 106 // this entry to this node. 107 if n.parent == nil && n.left == nil && n.right == nil && !n.taken { 108 n.entry = entry 109 n.taken = true 110 n.weight = entry.weight 111 newnode = n 112 return 113 } 114 115 n.weight = n.weight.Add(entry.weight) 116 117 // If the current node is empty, add the entry but don't increase the 118 // count. 119 if !n.taken { 120 n.taken = true 121 n.entry = entry 122 newnode = n 123 return 124 } 125 126 // Insert the element into the lest populated side. 127 if n.left == nil { 128 n.left = createNode(n, entry) 129 nodesAdded = 1 130 newnode = n.left 131 } else if n.right == nil { 132 n.right = createNode(n, entry) 133 nodesAdded = 1 134 newnode = n.right 135 } else if n.left.count <= n.right.count { 136 nodesAdded, newnode = n.left.recursiveInsert(entry) 137 } else { 138 nodesAdded, newnode = n.right.recursiveInsert(entry) 139 } 140 141 n.count += nodesAdded 142 return 143 } 144 145 // nodeAtWeight grabs an element in the tree that appears at the given weight. 146 // Though the tree has an arbitrary sorting, a sufficiently random weight will 147 // pull a random element. The tree is searched through in a post-ordered way. 148 func (n *node) nodeAtWeight(weight types.Currency) *node { 149 // Sanity check - weight must be less than the total weight of the tree. 150 if weight.Cmp(n.weight) > 0 { 151 build.Critical("Node weight corruption") 152 return nil 153 } 154 155 // Check if the left or right child should be returned. 156 if n.left != nil { 157 if weight.Cmp(n.left.weight) < 0 { 158 return n.left.nodeAtWeight(weight) 159 } 160 weight = weight.Sub(n.left.weight) // Search from the 0th index of the right side. 161 } 162 if n.right != nil && weight.Cmp(n.right.weight) < 0 { 163 return n.right.nodeAtWeight(weight) 164 } 165 166 // Should we panic here instead? 167 if !n.taken { 168 build.Critical("Node tree structure corruption") 169 return nil 170 } 171 172 // Return the root entry. 173 return n 174 } 175 176 // remove takes a node and removes it from the tree by climbing through the 177 // list of parents. remove does not delete nodes. 178 func (n *node) remove() { 179 n.weight = n.weight.Sub(n.entry.weight) 180 n.taken = false 181 current := n.parent 182 for current != nil { 183 current.weight = current.weight.Sub(n.entry.weight) 184 current = current.parent 185 } 186 } 187 188 // All returns all of the hosts in the host tree, sorted by weight. 189 func (ht *HostTree) All() []modules.HostDBEntry { 190 ht.mu.Lock() 191 defer ht.mu.Unlock() 192 193 var he []hostEntry 194 for _, node := range ht.hosts { 195 he = append(he, *node.entry) 196 } 197 sort.Sort(byWeight(he)) 198 199 var entries []modules.HostDBEntry 200 for _, entry := range he { 201 entries = append(entries, entry.HostDBEntry) 202 } 203 return entries 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 212 entry := &hostEntry{ 213 HostDBEntry: hdbe, 214 weight: ht.weightFn(hdbe), 215 } 216 217 if _, exists := ht.hosts[string(entry.PublicKey.Key)]; exists { 218 return errHostExists 219 } 220 221 _, node := ht.root.recursiveInsert(entry) 222 223 ht.hosts[string(entry.PublicKey.Key)] = node 224 return nil 225 } 226 227 // Remove removes the host with the public key provided by `pk`. 228 func (ht *HostTree) Remove(pk types.SiaPublicKey) error { 229 ht.mu.Lock() 230 defer ht.mu.Unlock() 231 232 node, exists := ht.hosts[string(pk.Key)] 233 if !exists { 234 return errNoSuchHost 235 } 236 node.remove() 237 delete(ht.hosts, string(pk.Key)) 238 239 return nil 240 } 241 242 // Modify updates a host entry at the given public key, replacing the old entry 243 // with the entry provided by `newEntry`. 244 func (ht *HostTree) Modify(hdbe modules.HostDBEntry) error { 245 ht.mu.Lock() 246 defer ht.mu.Unlock() 247 248 node, exists := ht.hosts[string(hdbe.PublicKey.Key)] 249 if !exists { 250 return errNoSuchHost 251 } 252 253 node.remove() 254 255 entry := &hostEntry{ 256 HostDBEntry: hdbe, 257 weight: ht.weightFn(hdbe), 258 } 259 260 _, node = ht.root.recursiveInsert(entry) 261 262 ht.hosts[string(entry.PublicKey.Key)] = node 263 return nil 264 } 265 266 // Select returns the host with the provided public key, should the host exist. 267 func (ht *HostTree) Select(spk types.SiaPublicKey) (modules.HostDBEntry, bool) { 268 ht.mu.Lock() 269 defer ht.mu.Unlock() 270 271 node, exists := ht.hosts[string(spk.Key)] 272 if !exists { 273 return modules.HostDBEntry{}, false 274 } 275 return node.entry.HostDBEntry, true 276 } 277 278 // SelectRandom grabs a random n hosts from the tree. There will be no repeats, but 279 // the length of the slice returned may be less than n, and may even be zero. 280 // The hosts that are returned first have the higher priority. Hosts passed to 281 // 'ignore' will not be considered; pass `nil` if no blacklist is desired. 282 func (ht *HostTree) SelectRandom(n int, ignore []types.SiaPublicKey) []modules.HostDBEntry { 283 ht.mu.Lock() 284 defer ht.mu.Unlock() 285 286 var hosts []modules.HostDBEntry 287 var removedEntries []*hostEntry 288 289 for _, pubkey := range ignore { 290 node, exists := ht.hosts[string(pubkey.Key)] 291 if !exists { 292 continue 293 } 294 node.remove() 295 delete(ht.hosts, string(pubkey.Key)) 296 removedEntries = append(removedEntries, node.entry) 297 } 298 299 for len(hosts) < n && len(ht.hosts) > 0 { 300 randWeight := fastrand.BigIntn(ht.root.weight.Big()) 301 node := ht.root.nodeAtWeight(types.NewCurrency(randWeight)) 302 303 if node.entry.AcceptingContracts && 304 len(node.entry.ScanHistory) > 0 && 305 node.entry.ScanHistory[len(node.entry.ScanHistory)-1].Success { 306 // The host must be online and accepting contracts to be returned 307 // by the random function. 308 hosts = append(hosts, node.entry.HostDBEntry) 309 } 310 311 removedEntries = append(removedEntries, node.entry) 312 node.remove() 313 delete(ht.hosts, string(node.entry.PublicKey.Key)) 314 } 315 316 for _, entry := range removedEntries { 317 _, node := ht.root.recursiveInsert(entry) 318 ht.hosts[string(entry.PublicKey.Key)] = node 319 } 320 321 return hosts 322 }