github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/kad_table.go (about) 1 // Copyright (C) 2013-2017, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 // 4 // This code is adapted from the libp2p project, specifically: 5 // https://github.com/libp2p/go-libp2p-kbucket/table.go 6 // we don't need to unify keyspaces between random strings and peer.IDs which ipfs requires. 7 //---------------------------------------------------------------------------------------- 8 9 package holochain 10 11 import ( 12 "container/list" 13 "fmt" 14 . "github.com/holochain/holochain-proto/hash" 15 peer "github.com/libp2p/go-libp2p-peer" 16 pstore "github.com/libp2p/go-libp2p-peerstore" 17 "sort" 18 "sync" 19 "time" 20 ) 21 22 // RoutingTable defines the routing table. 23 type RoutingTable struct { 24 25 // ID of the local peer 26 local peer.ID 27 28 // Blanket lock, refine later for better performance 29 tabLock sync.RWMutex 30 31 // latency metrics 32 metrics pstore.Metrics 33 34 // Maximum acceptable latency for peers in this cluster 35 maxLatency time.Duration 36 37 // kBuckets define all the fingers to other nodes. 38 Buckets []*Bucket 39 bucketsize int 40 41 // notification functions 42 PeerRemoved func(peer.ID) 43 PeerAdded func(peer.ID) 44 } 45 46 // NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance. 47 func NewRoutingTable(bucketsize int, localID peer.ID, latency time.Duration, m pstore.Metrics) *RoutingTable { 48 rt := &RoutingTable{ 49 Buckets: []*Bucket{newBucket()}, 50 bucketsize: bucketsize, 51 local: localID, 52 maxLatency: latency, 53 metrics: m, 54 PeerRemoved: func(peer.ID) {}, 55 PeerAdded: func(peer.ID) {}, 56 } 57 58 return rt 59 } 60 61 func commonPrefixLen(a, b peer.ID) int { 62 return ZeroPrefixLen(XOR([]byte(a), []byte(b))) 63 } 64 65 // Update adds or moves the given peer to the front of its respective bucket 66 // If a peer gets removed from a bucket, it is returned 67 func (rt *RoutingTable) Update(p peer.ID) { 68 cpl := commonPrefixLen(p, rt.local) 69 70 rt.tabLock.Lock() 71 defer rt.tabLock.Unlock() 72 bucketID := cpl 73 if bucketID >= len(rt.Buckets) { 74 bucketID = len(rt.Buckets) - 1 75 } 76 77 bucket := rt.Buckets[bucketID] 78 if bucket.Has(p) { 79 // If the peer is already in the table, move it to the front. 80 // This signifies that it it "more active" and the less active nodes 81 // Will as a result tend towards the back of the list 82 bucket.MoveToFront(p) 83 return 84 } 85 86 if rt.metrics.LatencyEWMA(p) > rt.maxLatency { 87 // Connection doesn't meet requirements, skip! 88 return 89 } 90 91 // New peer, add to bucket 92 bucket.PushFront(p) 93 rt.PeerAdded(p) 94 95 // Are we past the max bucket size? 96 if bucket.Len() > rt.bucketsize { 97 // If this bucket is the rightmost bucket, and its full 98 // we need to split it and create a new bucket 99 if bucketID == len(rt.Buckets)-1 { 100 rt.PeerRemoved(rt.nextBucket()) 101 return 102 } else { 103 // If the bucket cant split kick out least active node 104 rt.PeerRemoved(bucket.PopBack()) 105 return 106 } 107 } 108 } 109 110 // Remove deletes a peer from the routing table. This is to be used 111 // when we are sure a node has disconnected completely. 112 func (rt *RoutingTable) Remove(p peer.ID) { 113 rt.tabLock.Lock() 114 defer rt.tabLock.Unlock() 115 cpl := commonPrefixLen(p, rt.local) 116 117 bucketID := cpl 118 if bucketID >= len(rt.Buckets) { 119 bucketID = len(rt.Buckets) - 1 120 } 121 122 bucket := rt.Buckets[bucketID] 123 bucket.Remove(p) 124 rt.PeerRemoved(p) 125 } 126 127 func (rt *RoutingTable) nextBucket() peer.ID { 128 bucket := rt.Buckets[len(rt.Buckets)-1] 129 newBucket := bucket.Split(len(rt.Buckets)-1, rt.local) 130 rt.Buckets = append(rt.Buckets, newBucket) 131 if newBucket.Len() > rt.bucketsize { 132 return rt.nextBucket() 133 } 134 135 // If all elements were on left side of split... 136 if bucket.Len() > rt.bucketsize { 137 return bucket.PopBack() 138 } 139 return "" 140 } 141 142 // Find a specific peer by ID or return nil 143 func (rt *RoutingTable) Find(id peer.ID) peer.ID { 144 srch := rt.NearestPeers(HashFromPeerID(id), 1) 145 if len(srch) == 0 || srch[0] != id { 146 return "" 147 } 148 return srch[0] 149 } 150 151 // NearestPeer returns a single peer that is nearest to the given Hash 152 func (rt *RoutingTable) NearestPeer(hash Hash) peer.ID { 153 peers := rt.NearestPeers(hash, 1) 154 if len(peers) > 0 { 155 return peers[0] 156 } 157 158 Debugf("NearestPeer: Returning nil, table size = %d", rt.Size()) 159 return "" 160 } 161 162 func copyPeersFromList(target peer.ID, hashArr HashSorterArr, peerList *list.List) HashSorterArr { 163 center := HashFromPeerID(target) 164 for e := peerList.Front(); e != nil; e = e.Next() { 165 h := HashFromPeerID(e.Value.(peer.ID)) 166 pd := HashDistance{ 167 Hash: h, 168 Distance: HashXORDistance(h, center), 169 } 170 hashArr = append(hashArr, &pd) 171 } 172 return hashArr 173 } 174 175 func SortClosestPeers(peers []peer.ID, target Hash) []peer.ID { 176 var hsarr HashSorterArr 177 for _, p := range peers { 178 h := HashFromPeerID(p) 179 hd := &HashDistance{ 180 Hash: h, 181 Distance: HashXORDistance(h, target), 182 } 183 hsarr = append(hsarr, hd) 184 } 185 sort.Sort(hsarr) 186 var out []peer.ID 187 for _, p := range hsarr { 188 out = append(out, PeerIDFromHash(p.Hash.(Hash))) 189 } 190 return out 191 } 192 193 // NearestPeers returns a list of the 'count' closest peers to the given ID 194 func (rt *RoutingTable) NearestPeers(hash Hash, count int) []peer.ID { 195 id := PeerIDFromHash(hash) 196 cpl := commonPrefixLen(id, rt.local) 197 198 rt.tabLock.RLock() 199 200 // Get bucket at cpl index or last bucket 201 var bucket *Bucket 202 if cpl >= len(rt.Buckets) { 203 cpl = len(rt.Buckets) - 1 204 } 205 bucket = rt.Buckets[cpl] 206 207 var hashArr HashSorterArr 208 hashArr = copyPeersFromList(id, hashArr, bucket.list) 209 if len(hashArr) < count { 210 // In the case of an unusual split, one bucket may be short or empty. 211 // if this happens, search both surrounding buckets for nearby peers 212 if cpl > 0 { 213 plist := rt.Buckets[cpl-1].list 214 hashArr = copyPeersFromList(id, hashArr, plist) 215 } 216 217 if cpl < len(rt.Buckets)-1 { 218 plist := rt.Buckets[cpl+1].list 219 hashArr = copyPeersFromList(id, hashArr, plist) 220 } 221 } 222 rt.tabLock.RUnlock() 223 224 // Sort by distance to local peer 225 sort.Sort(hashArr) 226 227 /*s := "" 228 for _, c := range hashArr { 229 p := PeerIDFromHash(c.Hash.(Hash)) 230 s += fmt.Sprintf("%v ", p.Pretty()[2:4]) 231 } 232 fmt.Printf("%s\n", s) 233 */ 234 235 var out []peer.ID 236 for i := 0; i < count && i < hashArr.Len(); i++ { 237 p := PeerIDFromHash(hashArr[i].Hash.(Hash)) 238 out = append(out, p) 239 } 240 241 return out 242 } 243 244 // Size returns the total number of peers in the routing table 245 func (rt *RoutingTable) Size() int { 246 var tot int 247 rt.tabLock.RLock() 248 for _, buck := range rt.Buckets { 249 tot += buck.Len() 250 } 251 rt.tabLock.RUnlock() 252 return tot 253 } 254 255 // IsEmpty returns bool 256 func (rt *RoutingTable) IsEmpty() (empty bool) { 257 rt.tabLock.RLock() 258 empty = true 259 for _, buck := range rt.Buckets { 260 if buck.Len() > 0 { 261 empty = false 262 break 263 } 264 } 265 rt.tabLock.RUnlock() 266 return 267 } 268 269 // ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table. 270 // NOTE: This is potentially unsafe... use at your own risk 271 func (rt *RoutingTable) ListPeers() []peer.ID { 272 var peers []peer.ID 273 rt.tabLock.RLock() 274 for _, buck := range rt.Buckets { 275 peers = append(peers, buck.Peers()...) 276 } 277 rt.tabLock.RUnlock() 278 return peers 279 } 280 281 // Print prints a descriptive statement about the provided RoutingTable 282 func (rt *RoutingTable) Print() { 283 fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency) 284 rt.tabLock.RLock() 285 286 for i, b := range rt.Buckets { 287 fmt.Printf("\tbucket: %d\n", i) 288 289 b.lk.RLock() 290 for e := b.list.Front(); e != nil; e = e.Next() { 291 p := e.Value.(peer.ID) 292 fmt.Printf("\t\t- %s %s\n", p.Pretty(), rt.metrics.LatencyEWMA(p).String()) 293 } 294 b.lk.RUnlock() 295 } 296 rt.tabLock.RUnlock() 297 }