github.com/klaytn/klaytn@v1.12.1/networks/p2p/discover/discover_storage_kademlia.go (about) 1 // Modifications Copyright 2019 The klaytn Authors 2 // Copyright 2015 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from p2p/discover/table.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package discover 22 23 import ( 24 "crypto/rand" 25 "net" 26 "sync" 27 "time" 28 29 "github.com/klaytn/klaytn/common" 30 "github.com/klaytn/klaytn/crypto" 31 "github.com/klaytn/klaytn/log" 32 "github.com/klaytn/klaytn/networks/p2p/netutil" 33 ) 34 35 const ( 36 tableIPLimit, tableSubnet = 10, 24 37 bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 38 // We keep buckets for the upper 1/15 of distances because 39 // it's very unlikely we'll ever encounter a node that's closer. 40 hashBits = len(common.Hash{}) * 8 41 nBuckets = hashBits / 15 // Number of buckets 42 bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket 43 44 seedMinTableTime = 5 * time.Minute 45 ) 46 47 type KademliaStorage struct { 48 targetType NodeType 49 tab *Table 50 buckets [nBuckets]*bucket 51 bucketsMu sync.Mutex 52 ips netutil.DistinctNetSet 53 noDiscover bool // if noDiscover is true, doesn't lookup new node. 54 localLogger log.Logger 55 } 56 57 func (s *KademliaStorage) init() { 58 s.localLogger = logger.NewWith("Discover", "Kademlia") 59 s.ips = netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit} 60 s.bucketsMu.Lock() 61 defer s.bucketsMu.Unlock() 62 for i := range s.buckets { 63 s.buckets[i] = &bucket{ 64 ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, 65 } 66 } 67 } 68 69 func (s *KademliaStorage) lookup(targetID NodeID, refreshIfEmpty bool, targetType NodeType) []*Node { 70 s.localLogger.Debug("lookup start", "StorageName", s.name(), "targetID", targetID, 71 "targetNodeType", nodeTypeName(targetType), "refreshIfEmpty", refreshIfEmpty) 72 var ( 73 target = crypto.Keccak256Hash(targetID[:]) 74 result *nodesByDistance 75 ) 76 77 for { 78 // generate initial result set 79 result = s.closest(target, bucketSize) 80 if len(result.entries) > 0 || !refreshIfEmpty { 81 break 82 } 83 // The result set is empty, all nodes were dropped, refresh. 84 // We actually wait for the refresh to complete here. The very 85 // first query will hit this case and run the bootstrapping 86 // logic. 87 <-s.tab.refresh() 88 refreshIfEmpty = false 89 } 90 return s.tab.findNewNode(result, targetID, targetType, true, bucketSize) 91 } 92 93 func (s *KademliaStorage) getNodes(max int) []*Node { 94 nbd := s.closest(crypto.Keccak256Hash(s.tab.self.ID[:]), max) 95 var ret []*Node 96 for _, nd := range nbd.entries { 97 if nd.NType == s.targetType { 98 ret = append(ret, nd) 99 } 100 } 101 return ret 102 } 103 104 func (s *KademliaStorage) doRevalidate() { 105 s.bucketsMu.Lock() 106 defer s.bucketsMu.Unlock() 107 108 last, bi := s.nodeToRevalidate() 109 if last == nil { 110 // No non-empty bucket found. 111 return 112 } 113 114 holdingTime := s.tab.db.bondTime(last.ID).Add(10 * time.Second) 115 if time.Now().Before(holdingTime) { 116 s.localLogger.Debug("skip revalidate", "StorageName", s.name()) 117 return 118 } 119 120 // Ping the selected node and wait for a pong. 121 err := s.tab.ping(last.ID, last.addr()) 122 b := s.bucketByIdx(bi) 123 if err == nil { 124 // The node responded, move it to the front. 125 s.localLogger.Debug("Revalidated node", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID) 126 b.bump(last) 127 return 128 } 129 // No reply received, pick a replacement or delete the node if there aren't 130 // any replacements. 131 if r := s.replace(b, last); r != nil { 132 s.localLogger.Debug("Replaced the node without any response", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP) 133 } else { 134 s.localLogger.Debug("Removed the node without any response", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID, "ip", last.IP) 135 } 136 } 137 138 func (s *KademliaStorage) setTargetNodeType(tType NodeType) { 139 s.targetType = tType 140 } 141 142 func (s *KademliaStorage) doRefresh() { 143 if s.noDiscover { 144 return 145 } 146 147 // Run self lookup to discover new neighbor nodes. 148 s.lookup(s.tab.self.ID, false, s.targetType) 149 150 // The Kademlia paper specifies that the bucket refresh should 151 // perform a lookup in the least recently used bucket. We cannot 152 // adhere to this because the findnode target is a 512bit value 153 // (not hash-sized) and it is not easily possible to generate a 154 // sha3 preimage that falls into a chosen bucket. 155 // We perform a few lookups with a random target instead. 156 for i := 0; i < 3; i++ { 157 var target NodeID 158 rand.Read(target[:]) 159 s.lookup(target, false, s.targetType) 160 } 161 } 162 163 func (s *KademliaStorage) nodeAll() (nodes []*Node) { 164 for _, b := range s.buckets { 165 nodes = append(nodes, b.entries...) 166 } 167 return nodes 168 } 169 170 // The caller must hold s.bucketMu 171 func (s *KademliaStorage) bucketByIdx(bi int) *bucket { 172 // TODO-Klaytn-Node range check 173 return s.buckets[bi] 174 } 175 176 // The caller must hold s.bucketMu 177 func (s *KademliaStorage) nodeToRevalidate() (n *Node, bi int) { 178 s.tab.randMu.Lock() 179 defer s.tab.randMu.Unlock() 180 181 for _, bi = range s.tab.rand.Perm(len(s.buckets)) { // TODO 182 b := s.buckets[bi] 183 if len(b.entries) > 0 { 184 last := b.entries[len(b.entries)-1] 185 return last, bi 186 } 187 } 188 return nil, 0 189 } 190 191 func (s *KademliaStorage) copyBondedNodes() { 192 s.bucketsMu.Lock() 193 defer s.bucketsMu.Unlock() 194 now := time.Now() 195 for _, b := range &s.buckets { 196 for _, n := range b.entries { 197 if now.Sub(n.addedAt) >= seedMinTableTime { 198 s.tab.db.updateNode(n) 199 } 200 } 201 } 202 } 203 204 func (s *KademliaStorage) len() (n int) { 205 for _, b := range &s.buckets { 206 n += len(b.entries) 207 } 208 return n 209 } 210 211 func (s *KademliaStorage) getReplacements() []*Node { 212 s.bucketsMu.Lock() 213 defer s.bucketsMu.Unlock() 214 var nodes []*Node 215 for i := 0; i < nBuckets; i++ { 216 nodes = append(nodes, s.buckets[i].replacements...) 217 } 218 return nodes 219 } 220 221 func (s *KademliaStorage) getBucketEntries() []*Node { 222 s.bucketsMu.Lock() 223 defer s.bucketsMu.Unlock() 224 225 var nodes []*Node 226 for i := 0; i < nBuckets; i++ { 227 nodes = append(nodes, s.buckets[i].entries...) 228 } 229 return nodes 230 } 231 232 func (s *KademliaStorage) stuff(nodes []*Node) { 233 s.bucketsMu.Lock() 234 defer s.bucketsMu.Unlock() 235 236 for _, n := range nodes { 237 if n.ID == s.tab.self.ID { 238 continue // don't add self 239 } 240 b := s.bucket(n.sha) 241 if len(b.entries) < bucketSize { 242 s.bumpOrAdd(b, n) 243 } 244 245 } 246 } 247 248 // replace removes n from the replacement list and replaces 'last' with it if it is the 249 // last entry in the bucket. If 'last' isn't the last entry, it has either been replaced 250 // with someone else or became active. 251 func (s *KademliaStorage) replace(b *bucket, last *Node) *Node { 252 if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID != last.ID { 253 // Entry has moved, don't replace it. 254 return nil 255 } 256 // Still the last entry. 257 if len(b.replacements) == 0 { 258 s.deleteInBucket(b, last) 259 return nil 260 } 261 s.tab.randMu.Lock() 262 r := b.replacements[s.tab.rand.Intn(len(b.replacements))] 263 s.tab.randMu.Unlock() 264 b.replacements = deleteNode(b.replacements, r) 265 b.entries[len(b.entries)-1] = r 266 s.removeIP(b, last.IP) 267 return r 268 } 269 270 func (s *KademliaStorage) delete(n *Node) { 271 s.bucketsMu.Lock() 272 defer s.bucketsMu.Unlock() 273 s.deleteInBucket(s.bucket(n.sha), n) 274 } 275 276 // The caller must hold tab.mutex. 277 func (s *KademliaStorage) deleteInBucket(b *bucket, n *Node) { 278 b.entries = deleteNode(b.entries, n) 279 s.removeIP(b, n.IP) // TODO-Klaytn-Node Does the IP is not lock? 280 } 281 282 // closest returns the n nodes in the table that are closest to the 283 // given id. The caller must hold s.bucketMu. 284 func (s *KademliaStorage) closest(target common.Hash, nresults int) *nodesByDistance { 285 // This is a very wasteful way to find the closest nodes but 286 // obviously correct. I believe that tree-based buckets would make 287 // this easier to implement efficiently. 288 // TODO-Klaytn-Node more efficient ways to obtain the closest nodes could be considered. 289 close := &nodesByDistance{target: target} 290 s.bucketsMu.Lock() 291 defer s.bucketsMu.Unlock() 292 293 for _, b := range &s.buckets { 294 for _, n := range b.entries { 295 close.push(n, nresults) 296 } 297 } 298 return close 299 } 300 301 func (s *KademliaStorage) setTable(t *Table) { 302 s.tab = t 303 } 304 305 func (s *KademliaStorage) add(n *Node) { 306 s.bucketsMu.Lock() 307 defer s.bucketsMu.Unlock() 308 b := s.bucket(n.sha) 309 if !s.bumpOrAdd(b, n) { 310 // Node is not in table. Add it to the replacement list. 311 s.addReplacement(b, n) 312 } 313 } 314 315 func (s *KademliaStorage) readRandomNodes(buf []*Node) (n int) { 316 s.bucketsMu.Lock() 317 defer s.bucketsMu.Unlock() 318 319 // Find all non-empty buckets and get a fresh slice of their entries. 320 var buckets [][]*Node 321 for _, b := range &s.buckets { 322 if len(b.entries) > 0 { 323 buckets = append(buckets, b.entries[:]) 324 } 325 } 326 if len(buckets) == 0 { 327 return 0 328 } 329 // Shuffle the buckets. 330 s.tab.randMu.Lock() 331 for i := len(buckets) - 1; i > 0; i-- { 332 j := s.tab.rand.Intn(len(buckets)) 333 buckets[i], buckets[j] = buckets[j], buckets[i] 334 } 335 s.tab.randMu.Unlock() 336 // Move head of each bucket into buf, removing buckets that become empty. 337 var i, j int 338 for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { 339 b := buckets[j] 340 buf[i] = &(*b[0]) 341 buckets[j] = b[1:] 342 if len(b) == 1 { 343 buckets = append(buckets[:j], buckets[j+1:]...) 344 } 345 if len(buckets) == 0 { 346 break 347 } 348 } 349 return i + 1 350 } 351 352 // The caller must hold s.bucketMu 353 func (s *KademliaStorage) bucket(sha common.Hash) *bucket { 354 d := logdist(s.tab.self.sha, sha) 355 if d <= bucketMinDistance { 356 return s.buckets[0] 357 } 358 return s.buckets[d-bucketMinDistance-1] 359 } 360 361 // bumpOrAdd moves n to the front of the bucket entry list or adds it if the list isn't 362 // full. The return value is true if n is in the bucket. 363 // The caller must hold s.bucketMu 364 func (s *KademliaStorage) bumpOrAdd(b *bucket, n *Node) bool { 365 if b.bump(n) { 366 s.localLogger.Trace("Add(Bumped)", "StorageName", s.name(), "node", n) 367 return true 368 } 369 if len(b.entries) >= bucketSize || !s.addIP(b, n.IP) { 370 s.localLogger.Debug("Add(New) -Exceed Max", "StorageName", s.name(), "node", n) 371 return false 372 } 373 s.localLogger.Trace("Add(New)", "StorageName", s.name(), "node", n) 374 b.entries, _ = pushNode(b.entries, n, bucketSize) 375 b.replacements = deleteNode(b.replacements, n) 376 n.addedAt = time.Now() 377 if s.tab.nodeAddedHook != nil { 378 s.tab.nodeAddedHook(n) 379 } 380 return true 381 } 382 383 // The caller must hold s.bucketMu. 384 func (s *KademliaStorage) addReplacement(b *bucket, n *Node) { 385 for _, e := range b.replacements { 386 if e.ID == n.ID { 387 return // already in list 388 } 389 } 390 if !s.addIP(b, n.IP) { 391 return 392 } 393 var removed *Node 394 b.replacements, removed = pushNode(b.replacements, n, maxReplacements) 395 if removed != nil { 396 s.removeIP(b, removed.IP) 397 } 398 } 399 400 func (s *KademliaStorage) addIP(b *bucket, ip net.IP) bool { 401 if netutil.IsLAN(ip) { 402 return true 403 } 404 if !s.ips.Add(ip) { 405 s.localLogger.Debug("IP exceeds table limit", "StorageName", s.name(), "ip", ip) 406 return false 407 } 408 if !b.ips.Add(ip) { 409 s.localLogger.Debug("IP exceeds bucket limit", "StorageName", s.name(), "ip", ip) 410 s.ips.Remove(ip) 411 return false 412 } 413 return true 414 } 415 416 func (s *KademliaStorage) removeIP(b *bucket, ip net.IP) { 417 if netutil.IsLAN(ip) { 418 return 419 } 420 s.ips.Remove(ip) 421 b.ips.Remove(ip) 422 } 423 424 func (s *KademliaStorage) name() string { 425 return nodeTypeName(s.targetType) 426 } 427 428 // bucket contains nodes, ordered by their last activity. the entry 429 // that was most recently active is the first element in entries. 430 type bucket struct { 431 entries []*Node // live entries, sorted by time of last contact 432 replacements []*Node // recently seen nodes to be used if revalidation fails 433 ips netutil.DistinctNetSet 434 } 435 436 // bump moves the given node to the front of the bucket entry list 437 // if it is contained in that list. 438 // caller 439 func (b *bucket) bump(n *Node) bool { 440 for i := range b.entries { 441 if b.entries[i].ID == n.ID { 442 // move it to the front 443 copy(b.entries[1:], b.entries[:i]) 444 b.entries[0] = n 445 return true 446 } 447 } 448 return false 449 } 450 451 func (s *KademliaStorage) isAuthorized(id NodeID) bool { return true } 452 func (s *KademliaStorage) getAuthorizedNodes() []*Node { return nil } 453 func (s *KademliaStorage) putAuthorizedNode(*Node) {} 454 func (s *KademliaStorage) deleteAuthorizedNode(NodeID) {}