github.com/janotchain/janota@v0.0.0-20220824112012-93ea4c5dee78/swarm/network/kademlia/kademlia.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 kademlia 18 19 import ( 20 "fmt" 21 "sort" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/ethereum/go-ethereum/log" 27 ) 28 29 const ( 30 bucketSize = 4 31 proxBinSize = 2 32 maxProx = 8 33 connRetryExp = 2 34 maxPeers = 100 35 ) 36 37 var ( 38 purgeInterval = 42 * time.Hour 39 initialRetryInterval = 42 * time.Millisecond 40 maxIdleInterval = 42 * 1000 * time.Millisecond 41 // maxIdleInterval = 42 * 10 0 * time.Millisecond 42 ) 43 44 type KadParams struct { 45 // adjustable parameters 46 MaxProx int 47 ProxBinSize int 48 BucketSize int 49 PurgeInterval time.Duration 50 InitialRetryInterval time.Duration 51 MaxIdleInterval time.Duration 52 ConnRetryExp int 53 } 54 55 func NewKadParams() *KadParams { 56 return &KadParams{ 57 MaxProx: maxProx, 58 ProxBinSize: proxBinSize, 59 BucketSize: bucketSize, 60 PurgeInterval: purgeInterval, 61 InitialRetryInterval: initialRetryInterval, 62 MaxIdleInterval: maxIdleInterval, 63 ConnRetryExp: connRetryExp, 64 } 65 } 66 67 // Kademlia is a table of active nodes 68 type Kademlia struct { 69 addr Address // immutable baseaddress of the table 70 *KadParams // Kademlia configuration parameters 71 proxLimit int // state, the PO of the first row of the most proximate bin 72 proxSize int // state, the number of peers in the most proximate bin 73 count int // number of active peers (w live connection) 74 buckets [][]Node // the actual bins 75 db *KadDb // kaddb, node record database 76 lock sync.RWMutex // mutex to access buckets 77 } 78 79 type Node interface { 80 Addr() Address 81 Url() string 82 LastActive() time.Time 83 Drop() 84 } 85 86 // public constructor 87 // add is the base address of the table 88 // params is KadParams configuration 89 func New(addr Address, params *KadParams) *Kademlia { 90 buckets := make([][]Node, params.MaxProx+1) 91 return &Kademlia{ 92 addr: addr, 93 KadParams: params, 94 buckets: buckets, 95 db: newKadDb(addr, params), 96 } 97 } 98 99 // accessor for KAD base address 100 func (self *Kademlia) Addr() Address { 101 return self.addr 102 } 103 104 // accessor for KAD active node count 105 func (self *Kademlia) Count() int { 106 defer self.lock.Unlock() 107 self.lock.Lock() 108 return self.count 109 } 110 111 // accessor for KAD active node count 112 func (self *Kademlia) DBCount() int { 113 return self.db.count() 114 } 115 116 // On is the entry point called when a new nodes is added 117 // unsafe in that node is not checked to be already active node (to be called once) 118 func (self *Kademlia) On(node Node, cb func(*NodeRecord, Node) error) (err error) { 119 log.Debug(fmt.Sprintf("%v", self)) 120 defer self.lock.Unlock() 121 self.lock.Lock() 122 123 index := self.proximityBin(node.Addr()) 124 record := self.db.findOrCreate(index, node.Addr(), node.Url()) 125 126 if cb != nil { 127 err = cb(record, node) 128 log.Trace(fmt.Sprintf("cb(%v, %v) ->%v", record, node, err)) 129 if err != nil { 130 return fmt.Errorf("unable to add node %v, callback error: %v", node.Addr(), err) 131 } 132 log.Debug(fmt.Sprintf("add node record %v with node %v", record, node)) 133 } 134 135 // insert in kademlia table of active nodes 136 bucket := self.buckets[index] 137 // if bucket is full insertion replaces the worst node 138 // TODO: give priority to peers with active traffic 139 if len(bucket) < self.BucketSize { // >= allows us to add peers beyond the bucketsize limitation 140 self.buckets[index] = append(bucket, node) 141 log.Debug(fmt.Sprintf("add node %v to table", node)) 142 self.setProxLimit(index, true) 143 record.node = node 144 self.count++ 145 return nil 146 } 147 148 // always rotate peers 149 idle := self.MaxIdleInterval 150 var pos int 151 var replaced Node 152 for i, p := range bucket { 153 idleInt := time.Since(p.LastActive()) 154 if idleInt > idle { 155 idle = idleInt 156 pos = i 157 replaced = p 158 } 159 } 160 if replaced == nil { 161 log.Debug(fmt.Sprintf("all peers wanted, PO%03d bucket full", index)) 162 return fmt.Errorf("bucket full") 163 } 164 log.Debug(fmt.Sprintf("node %v replaced by %v (idle for %v > %v)", replaced, node, idle, self.MaxIdleInterval)) 165 replaced.Drop() 166 // actually replace in the row. When off(node) is called, the peer is no longer in the row 167 bucket[pos] = node 168 // there is no change in bucket cardinalities so no prox limit adjustment is needed 169 record.node = node 170 self.count++ 171 return nil 172 173 } 174 175 // Off is the called when a node is taken offline (from the protocol main loop exit) 176 func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) { 177 self.lock.Lock() 178 defer self.lock.Unlock() 179 180 index := self.proximityBin(node.Addr()) 181 bucket := self.buckets[index] 182 for i := 0; i < len(bucket); i++ { 183 if node.Addr() == bucket[i].Addr() { 184 self.buckets[index] = append(bucket[:i], bucket[(i+1):]...) 185 self.setProxLimit(index, false) 186 break 187 } 188 } 189 190 record := self.db.index[node.Addr()] 191 // callback on remove 192 if cb != nil { 193 cb(record, record.node) 194 } 195 record.node = nil 196 self.count-- 197 log.Debug(fmt.Sprintf("remove node %v from table, population now is %v", node, self.count)) 198 199 return 200 } 201 202 // proxLimit is dynamically adjusted so that 203 // 1) there is no empty buckets in bin < proxLimit and 204 // 2) the sum of all items are the minimum possible but higher than ProxBinSize 205 // adjust Prox (proxLimit and proxSize after an insertion/removal of nodes) 206 // caller holds the lock 207 func (self *Kademlia) setProxLimit(r int, on bool) { 208 // if the change is outside the core (PO lower) 209 // and the change does not leave a bucket empty then 210 // no adjustment needed 211 if r < self.proxLimit && len(self.buckets[r]) > 0 { 212 return 213 } 214 // if on=a node was added, then r must be within prox limit so increment cardinality 215 if on { 216 self.proxSize++ 217 curr := len(self.buckets[self.proxLimit]) 218 // if now core is big enough without the furthest bucket, then contract 219 // this can result in more than one bucket change 220 for self.proxSize >= self.ProxBinSize+curr && curr > 0 { 221 self.proxSize -= curr 222 self.proxLimit++ 223 curr = len(self.buckets[self.proxLimit]) 224 225 log.Trace(fmt.Sprintf("proxbin contraction (size: %v, limit: %v, bin: %v)", self.proxSize, self.proxLimit, r)) 226 } 227 return 228 } 229 // otherwise 230 if r >= self.proxLimit { 231 self.proxSize-- 232 } 233 // expand core by lowering prox limit until hit zero or cover the empty bucket or reached target cardinality 234 for (self.proxSize < self.ProxBinSize || r < self.proxLimit) && 235 self.proxLimit > 0 { 236 // 237 self.proxLimit-- 238 self.proxSize += len(self.buckets[self.proxLimit]) 239 log.Trace(fmt.Sprintf("proxbin expansion (size: %v, limit: %v, bin: %v)", self.proxSize, self.proxLimit, r)) 240 } 241 } 242 243 /* 244 returns the list of nodes belonging to the same proximity bin 245 as the target. The most proximate bin will be the union of the bins between 246 proxLimit and MaxProx. 247 */ 248 func (self *Kademlia) FindClosest(target Address, max int) []Node { 249 self.lock.Lock() 250 defer self.lock.Unlock() 251 252 r := nodesByDistance{ 253 target: target, 254 } 255 256 po := self.proximityBin(target) 257 index := po 258 step := 1 259 log.Trace(fmt.Sprintf("serving %v nodes at %v (PO%02d)", max, index, po)) 260 261 // if max is set to 0, just want a full bucket, dynamic number 262 min := max 263 // set limit to max 264 limit := max 265 if max == 0 { 266 min = 1 267 limit = maxPeers 268 } 269 270 var n int 271 for index >= 0 { 272 // add entire bucket 273 for _, p := range self.buckets[index] { 274 r.push(p, limit) 275 n++ 276 } 277 // terminate if index reached the bottom or enough peers > min 278 log.Trace(fmt.Sprintf("add %v -> %v (PO%02d, PO%03d)", len(self.buckets[index]), n, index, po)) 279 if n >= min && (step < 0 || max == 0) { 280 break 281 } 282 // reach top most non-empty PO bucket, turn around 283 if index == self.MaxProx { 284 index = po 285 step = -1 286 } 287 index += step 288 } 289 log.Trace(fmt.Sprintf("serve %d (<=%d) nodes for target lookup %v (PO%03d)", n, max, target, po)) 290 return r.nodes 291 } 292 293 func (self *Kademlia) Suggest() (*NodeRecord, bool, int) { 294 defer self.lock.RUnlock() 295 self.lock.RLock() 296 return self.db.findBest(self.BucketSize, func(i int) int { return len(self.buckets[i]) }) 297 } 298 299 // adds node records to kaddb (persisted node record db) 300 func (self *Kademlia) Add(nrs []*NodeRecord) { 301 self.db.add(nrs, self.proximityBin) 302 } 303 304 // nodesByDistance is a list of nodes, ordered by distance to target. 305 type nodesByDistance struct { 306 nodes []Node 307 target Address 308 } 309 310 func sortedByDistanceTo(target Address, slice []Node) bool { 311 var last Address 312 for i, node := range slice { 313 if i > 0 { 314 if target.ProxCmp(node.Addr(), last) < 0 { 315 return false 316 } 317 } 318 last = node.Addr() 319 } 320 return true 321 } 322 323 // push(node, max) adds the given node to the list, keeping the total size 324 // below max elements. 325 func (h *nodesByDistance) push(node Node, max int) { 326 // returns the firt index ix such that func(i) returns true 327 ix := sort.Search(len(h.nodes), func(i int) bool { 328 return h.target.ProxCmp(h.nodes[i].Addr(), node.Addr()) >= 0 329 }) 330 331 if len(h.nodes) < max { 332 h.nodes = append(h.nodes, node) 333 } 334 if ix < len(h.nodes) { 335 copy(h.nodes[ix+1:], h.nodes[ix:]) 336 h.nodes[ix] = node 337 } 338 } 339 340 /* 341 Taking the proximity order relative to a fix point x classifies the points in 342 the space (n byte long byte sequences) into bins. Items in each are at 343 most half as distant from x as items in the previous bin. Given a sample of 344 uniformly distributed items (a hash function over arbitrary sequence) the 345 proximity scale maps onto series of subsets with cardinalities on a negative 346 exponential scale. 347 348 It also has the property that any two item belonging to the same bin are at 349 most half as distant from each other as they are from x. 350 351 If we think of random sample of items in the bins as connections in a network of interconnected nodes than relative proximity can serve as the basis for local 352 decisions for graph traversal where the task is to find a route between two 353 points. Since in every hop, the finite distance halves, there is 354 a guaranteed constant maximum limit on the number of hops needed to reach one 355 node from the other. 356 */ 357 358 func (self *Kademlia) proximityBin(other Address) (ret int) { 359 ret = proximity(self.addr, other) 360 if ret > self.MaxProx { 361 ret = self.MaxProx 362 } 363 return 364 } 365 366 // provides keyrange for chunk db iteration 367 func (self *Kademlia) KeyRange(other Address) (start, stop Address) { 368 defer self.lock.RUnlock() 369 self.lock.RLock() 370 return KeyRange(self.addr, other, self.proxLimit) 371 } 372 373 // save persists kaddb on disk (written to file on path in json format. 374 func (self *Kademlia) Save(path string, cb func(*NodeRecord, Node)) error { 375 return self.db.save(path, cb) 376 } 377 378 // Load(path) loads the node record database (kaddb) from file on path. 379 func (self *Kademlia) Load(path string, cb func(*NodeRecord, Node) error) (err error) { 380 return self.db.load(path, cb) 381 } 382 383 // kademlia table + kaddb table displayed with ascii 384 func (self *Kademlia) String() string { 385 defer self.lock.RUnlock() 386 self.lock.RLock() 387 defer self.db.lock.RUnlock() 388 self.db.lock.RLock() 389 390 var rows []string 391 rows = append(rows, "=========================================================================") 392 rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %v", time.Now().UTC().Format(time.UnixDate), self.addr.String()[:6])) 393 rows = append(rows, fmt.Sprintf("population: %d (%d), proxLimit: %d, proxSize: %d", self.count, len(self.db.index), self.proxLimit, self.proxSize)) 394 rows = append(rows, fmt.Sprintf("MaxProx: %d, ProxBinSize: %d, BucketSize: %d", self.MaxProx, self.ProxBinSize, self.BucketSize)) 395 396 for i, bucket := range self.buckets { 397 398 if i == self.proxLimit { 399 rows = append(rows, fmt.Sprintf("============ PROX LIMIT: %d ==========================================", i)) 400 } 401 row := []string{fmt.Sprintf("%03d", i), fmt.Sprintf("%2d", len(bucket))} 402 var k int 403 c := self.db.cursors[i] 404 for ; k < len(bucket); k++ { 405 p := bucket[(c+k)%len(bucket)] 406 row = append(row, p.Addr().String()[:6]) 407 if k == 4 { 408 break 409 } 410 } 411 for ; k < 4; k++ { 412 row = append(row, " ") 413 } 414 row = append(row, fmt.Sprintf("| %2d %2d", len(self.db.Nodes[i]), self.db.cursors[i])) 415 416 for j, p := range self.db.Nodes[i] { 417 row = append(row, p.Addr.String()[:6]) 418 if j == 3 { 419 break 420 } 421 } 422 rows = append(rows, strings.Join(row, " ")) 423 if i == self.MaxProx { 424 } 425 } 426 rows = append(rows, "=========================================================================") 427 return strings.Join(rows, "\n") 428 }