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