github.com/core-coin/go-core/v2@v2.1.9/p2p/discv5/table.go (about) 1 // Copyright 2016 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package discv5 is a prototype implementation of Discvery v5. 18 // Deprecated: do not use this package. 19 package discv5 20 21 import ( 22 "crypto/rand" 23 "encoding/binary" 24 "fmt" 25 "net" 26 "sort" 27 28 "github.com/core-coin/go-core/v2/common" 29 ) 30 31 const ( 32 alpha = 3 // Kademlia concurrency factor 33 bucketSize = 16 // Kademlia bucket size 34 hashBits = len(common.Hash{}) * 8 35 nBuckets = hashBits + 1 // Number of buckets 36 37 maxFindnodeFailures = 5 38 ) 39 40 type Table struct { 41 count int // number of nodes 42 buckets [nBuckets]*bucket // index of known nodes by distance 43 nodeAddedHook func(*Node) // for testing 44 self *Node // metadata of the local node 45 } 46 47 // bucket contains nodes, ordered by their last activity. the entry 48 // that was most recently active is the first element in entries. 49 type bucket struct { 50 entries []*Node 51 replacements []*Node 52 } 53 54 func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table { 55 self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)) 56 tab := &Table{self: self} 57 for i := range tab.buckets { 58 tab.buckets[i] = new(bucket) 59 } 60 return tab 61 } 62 63 const printTable = false 64 65 // chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia 66 // buckets filled with live connections and keep the network topology healthy. 67 // This requires selecting addresses closer to our own with a higher probability 68 // in order to refresh closer buckets too. 69 // 70 // This algorithm approximates the distance distribution of existing nodes in the 71 // table by selecting a random node from the table and selecting a target address 72 // with a distance less than twice of that of the selected node. 73 // This algorithm will be improved later to specifically target the least recently 74 // used buckets. 75 func (tab *Table) chooseBucketRefreshTarget() common.Hash { 76 entries := 0 77 if printTable { 78 fmt.Println() 79 } 80 for i, b := range &tab.buckets { 81 entries += len(b.entries) 82 if printTable { 83 for _, e := range b.entries { 84 fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex()) 85 } 86 } 87 } 88 89 prefix := binary.BigEndian.Uint64(tab.self.sha[0:8]) 90 dist := ^uint64(0) 91 entry := int(randUint(uint32(entries + 1))) 92 for _, b := range &tab.buckets { 93 if entry < len(b.entries) { 94 n := b.entries[entry] 95 dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix 96 break 97 } 98 entry -= len(b.entries) 99 } 100 101 ddist := ^uint64(0) 102 if dist+dist > dist { 103 ddist = dist 104 } 105 targetPrefix := prefix ^ randUint64n(ddist) 106 107 var target common.Hash 108 binary.BigEndian.PutUint64(target[0:8], targetPrefix) 109 rand.Read(target[8:]) 110 return target 111 } 112 113 // readRandomNodes fills the given slice with random nodes from the 114 // table. It will not write the same node more than once. The nodes in 115 // the slice are copies and can be modified by the caller. 116 func (tab *Table) readRandomNodes(buf []*Node) (n int) { 117 // TODO: tree-based buckets would help here 118 // Find all non-empty buckets and get a fresh slice of their entries. 119 var buckets [][]*Node 120 for _, b := range &tab.buckets { 121 if len(b.entries) > 0 { 122 buckets = append(buckets, b.entries) 123 } 124 } 125 if len(buckets) == 0 { 126 return 0 127 } 128 // Shuffle the buckets. 129 for i := uint32(len(buckets)) - 1; i > 0; i-- { 130 j := randUint(i) 131 buckets[i], buckets[j] = buckets[j], buckets[i] 132 } 133 // Move head of each bucket into buf, removing buckets that become empty. 134 var i, j int 135 for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { 136 b := buckets[j] 137 buf[i] = &(*b[0]) 138 buckets[j] = b[1:] 139 if len(b) == 1 { 140 buckets = append(buckets[:j], buckets[j+1:]...) 141 } 142 if len(buckets) == 0 { 143 break 144 } 145 } 146 return i + 1 147 } 148 149 func randUint(max uint32) uint32 { 150 if max < 2 { 151 return 0 152 } 153 var b [4]byte 154 rand.Read(b[:]) 155 return binary.BigEndian.Uint32(b[:]) % max 156 } 157 158 func randUint64n(max uint64) uint64 { 159 if max < 2 { 160 return 0 161 } 162 var b [8]byte 163 rand.Read(b[:]) 164 return binary.BigEndian.Uint64(b[:]) % max 165 } 166 167 // closest returns the n nodes in the table that are closest to the 168 // given id. The caller must hold tab.mutex. 169 func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance { 170 // This is a very wasteful way to find the closest nodes but 171 // obviously correct. I believe that tree-based buckets would make 172 // this easier to implement efficiently. 173 close := &nodesByDistance{target: target} 174 for _, b := range &tab.buckets { 175 for _, n := range b.entries { 176 close.push(n, nresults) 177 } 178 } 179 return close 180 } 181 182 // add attempts to add the given node its corresponding bucket. If the 183 // bucket has space available, adding the node succeeds immediately. 184 // Otherwise, the node is added to the replacement cache for the bucket. 185 func (tab *Table) add(n *Node) (contested *Node) { 186 //fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex()) 187 if n.ID == tab.self.ID { 188 return 189 } 190 b := tab.buckets[logdist(tab.self.sha, n.sha)] 191 switch { 192 case b.bump(n): 193 // n exists in b. 194 return nil 195 case len(b.entries) < bucketSize: 196 // b has space available. 197 b.addFront(n) 198 tab.count++ 199 if tab.nodeAddedHook != nil { 200 tab.nodeAddedHook(n) 201 } 202 return nil 203 default: 204 // b has no space left, add to replacement cache 205 // and revalidate the last entry. 206 // TODO: drop previous node 207 b.replacements = append(b.replacements, n) 208 if len(b.replacements) > bucketSize { 209 copy(b.replacements, b.replacements[1:]) 210 b.replacements = b.replacements[:len(b.replacements)-1] 211 } 212 return b.entries[len(b.entries)-1] 213 } 214 } 215 216 // stuff adds nodes the table to the end of their corresponding bucket 217 // if the bucket is not full. 218 func (tab *Table) stuff(nodes []*Node) { 219 outer: 220 for _, n := range nodes { 221 if n.ID == tab.self.ID { 222 continue // don't add self 223 } 224 bucket := tab.buckets[logdist(tab.self.sha, n.sha)] 225 for i := range bucket.entries { 226 if bucket.entries[i].ID == n.ID { 227 continue outer // already in bucket 228 } 229 } 230 if len(bucket.entries) < bucketSize { 231 bucket.entries = append(bucket.entries, n) 232 tab.count++ 233 if tab.nodeAddedHook != nil { 234 tab.nodeAddedHook(n) 235 } 236 } 237 } 238 } 239 240 // delete removes an entry from the node table (used to evacuate 241 // failed/non-bonded discovery peers). 242 func (tab *Table) delete(node *Node) { 243 //fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex()) 244 bucket := tab.buckets[logdist(tab.self.sha, node.sha)] 245 for i := range bucket.entries { 246 if bucket.entries[i].ID == node.ID { 247 bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...) 248 tab.count-- 249 return 250 } 251 } 252 } 253 254 func (tab *Table) deleteReplace(node *Node) { 255 b := tab.buckets[logdist(tab.self.sha, node.sha)] 256 i := 0 257 for i < len(b.entries) { 258 if b.entries[i].ID == node.ID { 259 b.entries = append(b.entries[:i], b.entries[i+1:]...) 260 tab.count-- 261 } else { 262 i++ 263 } 264 } 265 // refill from replacement cache 266 // TODO: maybe use random index 267 if len(b.entries) < bucketSize && len(b.replacements) > 0 { 268 ri := len(b.replacements) - 1 269 b.addFront(b.replacements[ri]) 270 tab.count++ 271 b.replacements[ri] = nil 272 b.replacements = b.replacements[:ri] 273 } 274 } 275 276 func (b *bucket) addFront(n *Node) { 277 b.entries = append(b.entries, nil) 278 copy(b.entries[1:], b.entries) 279 b.entries[0] = n 280 } 281 282 func (b *bucket) bump(n *Node) bool { 283 for i := range b.entries { 284 if b.entries[i].ID == n.ID { 285 // move it to the front 286 copy(b.entries[1:], b.entries[:i]) 287 b.entries[0] = n 288 return true 289 } 290 } 291 return false 292 } 293 294 // nodesByDistance is a list of nodes, ordered by 295 // distance to target. 296 type nodesByDistance struct { 297 entries []*Node 298 target common.Hash 299 } 300 301 // push adds the given node to the list, keeping the total size below maxElems. 302 func (h *nodesByDistance) push(n *Node, maxElems int) { 303 ix := sort.Search(len(h.entries), func(i int) bool { 304 return distcmp(h.target, h.entries[i].sha, n.sha) > 0 305 }) 306 if len(h.entries) < maxElems { 307 h.entries = append(h.entries, n) 308 } 309 if ix == len(h.entries) { 310 // farther away than all nodes we already have. 311 // if there was room for it, the node is now the last element. 312 } else { 313 // slide existing entries down to make room 314 // this will overwrite the entry we just appended. 315 copy(h.entries[ix+1:], h.entries[ix:]) 316 h.entries[ix] = n 317 } 318 }