github.com/luckypickle/go-ethereum-vet@v1.14.2/les/serverpool.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 les implements the Light Ethereum Subprotocol. 18 package les 19 20 import ( 21 "fmt" 22 "io" 23 "math" 24 "math/rand" 25 "net" 26 "strconv" 27 "sync" 28 "time" 29 30 "github.com/luckypickle/go-ethereum-vet/common/mclock" 31 "github.com/luckypickle/go-ethereum-vet/ethdb" 32 "github.com/luckypickle/go-ethereum-vet/log" 33 "github.com/luckypickle/go-ethereum-vet/p2p" 34 "github.com/luckypickle/go-ethereum-vet/p2p/discover" 35 "github.com/luckypickle/go-ethereum-vet/p2p/discv5" 36 "github.com/luckypickle/go-ethereum-vet/rlp" 37 ) 38 39 const ( 40 // After a connection has been ended or timed out, there is a waiting period 41 // before it can be selected for connection again. 42 // waiting period = base delay * (1 + random(1)) 43 // base delay = shortRetryDelay for the first shortRetryCnt times after a 44 // successful connection, after that longRetryDelay is applied 45 shortRetryCnt = 5 46 shortRetryDelay = time.Second * 5 47 longRetryDelay = time.Minute * 10 48 // maxNewEntries is the maximum number of newly discovered (never connected) nodes. 49 // If the limit is reached, the least recently discovered one is thrown out. 50 maxNewEntries = 1000 51 // maxKnownEntries is the maximum number of known (already connected) nodes. 52 // If the limit is reached, the least recently connected one is thrown out. 53 // (not that unlike new entries, known entries are persistent) 54 maxKnownEntries = 1000 55 // target for simultaneously connected servers 56 targetServerCount = 5 57 // target for servers selected from the known table 58 // (we leave room for trying new ones if there is any) 59 targetKnownSelect = 3 60 // after dialTimeout, consider the server unavailable and adjust statistics 61 dialTimeout = time.Second * 30 62 // targetConnTime is the minimum expected connection duration before a server 63 // drops a client without any specific reason 64 targetConnTime = time.Minute * 10 65 // new entry selection weight calculation based on most recent discovery time: 66 // unity until discoverExpireStart, then exponential decay with discoverExpireConst 67 discoverExpireStart = time.Minute * 20 68 discoverExpireConst = time.Minute * 20 69 // known entry selection weight is dropped by a factor of exp(-failDropLn) after 70 // each unsuccessful connection (restored after a successful one) 71 failDropLn = 0.1 72 // known node connection success and quality statistics have a long term average 73 // and a short term value which is adjusted exponentially with a factor of 74 // pstatRecentAdjust with each dial/connection and also returned exponentially 75 // to the average with the time constant pstatReturnToMeanTC 76 pstatReturnToMeanTC = time.Hour 77 // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after 78 // each unsuccessful connection (restored after a successful one) 79 addrFailDropLn = math.Ln2 80 // responseScoreTC and delayScoreTC are exponential decay time constants for 81 // calculating selection chances from response times and block delay times 82 responseScoreTC = time.Millisecond * 100 83 delayScoreTC = time.Second * 5 84 timeoutPow = 10 85 // initStatsWeight is used to initialize previously unknown peers with good 86 // statistics to give a chance to prove themselves 87 initStatsWeight = 1 88 ) 89 90 // connReq represents a request for peer connection. 91 type connReq struct { 92 p *peer 93 ip net.IP 94 port uint16 95 result chan *poolEntry 96 } 97 98 // disconnReq represents a request for peer disconnection. 99 type disconnReq struct { 100 entry *poolEntry 101 stopped bool 102 done chan struct{} 103 } 104 105 // registerReq represents a request for peer registration. 106 type registerReq struct { 107 entry *poolEntry 108 done chan struct{} 109 } 110 111 // serverPool implements a pool for storing and selecting newly discovered and already 112 // known light server nodes. It received discovered nodes, stores statistics about 113 // known nodes and takes care of always having enough good quality servers connected. 114 type serverPool struct { 115 db ethdb.Database 116 dbKey []byte 117 server *p2p.Server 118 quit chan struct{} 119 wg *sync.WaitGroup 120 connWg sync.WaitGroup 121 122 topic discv5.Topic 123 124 discSetPeriod chan time.Duration 125 discNodes chan *discv5.Node 126 discLookups chan bool 127 128 entries map[discover.NodeID]*poolEntry 129 timeout, enableRetry chan *poolEntry 130 adjustStats chan poolStatAdjust 131 132 connCh chan *connReq 133 disconnCh chan *disconnReq 134 registerCh chan *registerReq 135 136 knownQueue, newQueue poolEntryQueue 137 knownSelect, newSelect *weightedRandomSelect 138 knownSelected, newSelected int 139 fastDiscover bool 140 } 141 142 // newServerPool creates a new serverPool instance 143 func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { 144 pool := &serverPool{ 145 db: db, 146 quit: quit, 147 wg: wg, 148 entries: make(map[discover.NodeID]*poolEntry), 149 timeout: make(chan *poolEntry, 1), 150 adjustStats: make(chan poolStatAdjust, 100), 151 enableRetry: make(chan *poolEntry, 1), 152 connCh: make(chan *connReq), 153 disconnCh: make(chan *disconnReq), 154 registerCh: make(chan *registerReq), 155 knownSelect: newWeightedRandomSelect(), 156 newSelect: newWeightedRandomSelect(), 157 fastDiscover: true, 158 } 159 pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) 160 pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) 161 return pool 162 } 163 164 func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { 165 pool.server = server 166 pool.topic = topic 167 pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) 168 pool.wg.Add(1) 169 pool.loadNodes() 170 171 if pool.server.DiscV5 != nil { 172 pool.discSetPeriod = make(chan time.Duration, 1) 173 pool.discNodes = make(chan *discv5.Node, 100) 174 pool.discLookups = make(chan bool, 100) 175 go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) 176 } 177 pool.checkDial() 178 go pool.eventLoop() 179 } 180 181 // connect should be called upon any incoming connection. If the connection has been 182 // dialed by the server pool recently, the appropriate pool entry is returned. 183 // Otherwise, the connection should be rejected. 184 // Note that whenever a connection has been accepted and a pool entry has been returned, 185 // disconnect should also always be called. 186 func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { 187 log.Debug("Connect new entry", "enode", p.id) 188 req := &connReq{p: p, ip: ip, port: port, result: make(chan *poolEntry, 1)} 189 select { 190 case pool.connCh <- req: 191 case <-pool.quit: 192 return nil 193 } 194 return <-req.result 195 } 196 197 // registered should be called after a successful handshake 198 func (pool *serverPool) registered(entry *poolEntry) { 199 log.Debug("Registered new entry", "enode", entry.id) 200 req := ®isterReq{entry: entry, done: make(chan struct{})} 201 select { 202 case pool.registerCh <- req: 203 case <-pool.quit: 204 return 205 } 206 <-req.done 207 } 208 209 // disconnect should be called when ending a connection. Service quality statistics 210 // can be updated optionally (not updated if no registration happened, in this case 211 // only connection statistics are updated, just like in case of timeout) 212 func (pool *serverPool) disconnect(entry *poolEntry) { 213 stopped := false 214 select { 215 case <-pool.quit: 216 stopped = true 217 default: 218 } 219 log.Debug("Disconnected old entry", "enode", entry.id) 220 req := &disconnReq{entry: entry, stopped: stopped, done: make(chan struct{})} 221 222 // Block until disconnection request is served. 223 pool.disconnCh <- req 224 <-req.done 225 } 226 227 const ( 228 pseBlockDelay = iota 229 pseResponseTime 230 pseResponseTimeout 231 ) 232 233 // poolStatAdjust records are sent to adjust peer block delay/response time statistics 234 type poolStatAdjust struct { 235 adjustType int 236 entry *poolEntry 237 time time.Duration 238 } 239 240 // adjustBlockDelay adjusts the block announce delay statistics of a node 241 func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) { 242 if entry == nil { 243 return 244 } 245 pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time} 246 } 247 248 // adjustResponseTime adjusts the request response time statistics of a node 249 func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) { 250 if entry == nil { 251 return 252 } 253 if timeout { 254 pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time} 255 } else { 256 pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time} 257 } 258 } 259 260 // eventLoop handles pool events and mutex locking for all internal functions 261 func (pool *serverPool) eventLoop() { 262 lookupCnt := 0 263 var convTime mclock.AbsTime 264 if pool.discSetPeriod != nil { 265 pool.discSetPeriod <- time.Millisecond * 100 266 } 267 268 // disconnect updates service quality statistics depending on the connection time 269 // and disconnection initiator. 270 disconnect := func(req *disconnReq, stopped bool) { 271 // Handle peer disconnection requests. 272 entry := req.entry 273 if entry.state == psRegistered { 274 connAdjust := float64(mclock.Now()-entry.regTime) / float64(targetConnTime) 275 if connAdjust > 1 { 276 connAdjust = 1 277 } 278 if stopped { 279 // disconnect requested by ourselves. 280 entry.connectStats.add(1, connAdjust) 281 } else { 282 // disconnect requested by server side. 283 entry.connectStats.add(connAdjust, 1) 284 } 285 } 286 entry.state = psNotConnected 287 288 if entry.knownSelected { 289 pool.knownSelected-- 290 } else { 291 pool.newSelected-- 292 } 293 pool.setRetryDial(entry) 294 pool.connWg.Done() 295 close(req.done) 296 } 297 298 for { 299 select { 300 case entry := <-pool.timeout: 301 if !entry.removed { 302 pool.checkDialTimeout(entry) 303 } 304 305 case entry := <-pool.enableRetry: 306 if !entry.removed { 307 entry.delayedRetry = false 308 pool.updateCheckDial(entry) 309 } 310 311 case adj := <-pool.adjustStats: 312 switch adj.adjustType { 313 case pseBlockDelay: 314 adj.entry.delayStats.add(float64(adj.time), 1) 315 case pseResponseTime: 316 adj.entry.responseStats.add(float64(adj.time), 1) 317 adj.entry.timeoutStats.add(0, 1) 318 case pseResponseTimeout: 319 adj.entry.timeoutStats.add(1, 1) 320 } 321 322 case node := <-pool.discNodes: 323 entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP) 324 pool.updateCheckDial(entry) 325 326 case conv := <-pool.discLookups: 327 if conv { 328 if lookupCnt == 0 { 329 convTime = mclock.Now() 330 } 331 lookupCnt++ 332 if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) { 333 pool.fastDiscover = false 334 if pool.discSetPeriod != nil { 335 pool.discSetPeriod <- time.Minute 336 } 337 } 338 } 339 340 case req := <-pool.connCh: 341 // Handle peer connection requests. 342 entry := pool.entries[req.p.ID()] 343 if entry == nil { 344 entry = pool.findOrNewNode(req.p.ID(), req.ip, req.port) 345 } 346 if entry.state == psConnected || entry.state == psRegistered { 347 req.result <- nil 348 continue 349 } 350 pool.connWg.Add(1) 351 entry.peer = req.p 352 entry.state = psConnected 353 addr := &poolEntryAddress{ 354 ip: req.ip, 355 port: req.port, 356 lastSeen: mclock.Now(), 357 } 358 entry.lastConnected = addr 359 entry.addr = make(map[string]*poolEntryAddress) 360 entry.addr[addr.strKey()] = addr 361 entry.addrSelect = *newWeightedRandomSelect() 362 entry.addrSelect.update(addr) 363 req.result <- entry 364 365 case req := <-pool.registerCh: 366 // Handle peer registration requests. 367 entry := req.entry 368 entry.state = psRegistered 369 entry.regTime = mclock.Now() 370 if !entry.known { 371 pool.newQueue.remove(entry) 372 entry.known = true 373 } 374 pool.knownQueue.setLatest(entry) 375 entry.shortRetry = shortRetryCnt 376 close(req.done) 377 378 case req := <-pool.disconnCh: 379 // Handle peer disconnection requests. 380 disconnect(req, req.stopped) 381 382 case <-pool.quit: 383 if pool.discSetPeriod != nil { 384 close(pool.discSetPeriod) 385 } 386 387 // Spawn a goroutine to close the disconnCh after all connections are disconnected. 388 go func() { 389 pool.connWg.Wait() 390 close(pool.disconnCh) 391 }() 392 393 // Handle all remaining disconnection requests before exit. 394 for req := range pool.disconnCh { 395 disconnect(req, true) 396 } 397 pool.saveNodes() 398 pool.wg.Done() 399 return 400 } 401 } 402 } 403 404 func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry { 405 now := mclock.Now() 406 entry := pool.entries[id] 407 if entry == nil { 408 log.Debug("Discovered new entry", "id", id) 409 entry = &poolEntry{ 410 id: id, 411 addr: make(map[string]*poolEntryAddress), 412 addrSelect: *newWeightedRandomSelect(), 413 shortRetry: shortRetryCnt, 414 } 415 pool.entries[id] = entry 416 // initialize previously unknown peers with good statistics to give a chance to prove themselves 417 entry.connectStats.add(1, initStatsWeight) 418 entry.delayStats.add(0, initStatsWeight) 419 entry.responseStats.add(0, initStatsWeight) 420 entry.timeoutStats.add(0, initStatsWeight) 421 } 422 entry.lastDiscovered = now 423 addr := &poolEntryAddress{ 424 ip: ip, 425 port: port, 426 } 427 if a, ok := entry.addr[addr.strKey()]; ok { 428 addr = a 429 } else { 430 entry.addr[addr.strKey()] = addr 431 } 432 addr.lastSeen = now 433 entry.addrSelect.update(addr) 434 if !entry.known { 435 pool.newQueue.setLatest(entry) 436 } 437 return entry 438 } 439 440 // loadNodes loads known nodes and their statistics from the database 441 func (pool *serverPool) loadNodes() { 442 enc, err := pool.db.Get(pool.dbKey) 443 if err != nil { 444 return 445 } 446 var list []*poolEntry 447 err = rlp.DecodeBytes(enc, &list) 448 if err != nil { 449 log.Debug("Failed to decode node list", "err", err) 450 return 451 } 452 for _, e := range list { 453 log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails, 454 "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight), 455 "delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight), 456 "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), 457 "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) 458 pool.entries[e.id] = e 459 pool.knownQueue.setLatest(e) 460 pool.knownSelect.update((*knownEntry)(e)) 461 } 462 } 463 464 // saveNodes saves known nodes and their statistics into the database. Nodes are 465 // ordered from least to most recently connected. 466 func (pool *serverPool) saveNodes() { 467 list := make([]*poolEntry, len(pool.knownQueue.queue)) 468 for i := range list { 469 list[i] = pool.knownQueue.fetchOldest() 470 } 471 enc, err := rlp.EncodeToBytes(list) 472 if err == nil { 473 pool.db.Put(pool.dbKey, enc) 474 } 475 } 476 477 // removeEntry removes a pool entry when the entry count limit is reached. 478 // Note that it is called by the new/known queues from which the entry has already 479 // been removed so removing it from the queues is not necessary. 480 func (pool *serverPool) removeEntry(entry *poolEntry) { 481 pool.newSelect.remove((*discoveredEntry)(entry)) 482 pool.knownSelect.remove((*knownEntry)(entry)) 483 entry.removed = true 484 delete(pool.entries, entry.id) 485 } 486 487 // setRetryDial starts the timer which will enable dialing a certain node again 488 func (pool *serverPool) setRetryDial(entry *poolEntry) { 489 delay := longRetryDelay 490 if entry.shortRetry > 0 { 491 entry.shortRetry-- 492 delay = shortRetryDelay 493 } 494 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 495 entry.delayedRetry = true 496 go func() { 497 select { 498 case <-pool.quit: 499 case <-time.After(delay): 500 select { 501 case <-pool.quit: 502 case pool.enableRetry <- entry: 503 } 504 } 505 }() 506 } 507 508 // updateCheckDial is called when an entry can potentially be dialed again. It updates 509 // its selection weights and checks if new dials can/should be made. 510 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 511 pool.newSelect.update((*discoveredEntry)(entry)) 512 pool.knownSelect.update((*knownEntry)(entry)) 513 pool.checkDial() 514 } 515 516 // checkDial checks if new dials can/should be made. It tries to select servers both 517 // based on good statistics and recent discovery. 518 func (pool *serverPool) checkDial() { 519 fillWithKnownSelects := !pool.fastDiscover 520 for pool.knownSelected < targetKnownSelect { 521 entry := pool.knownSelect.choose() 522 if entry == nil { 523 fillWithKnownSelects = false 524 break 525 } 526 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 527 } 528 for pool.knownSelected+pool.newSelected < targetServerCount { 529 entry := pool.newSelect.choose() 530 if entry == nil { 531 break 532 } 533 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 534 } 535 if fillWithKnownSelects { 536 // no more newly discovered nodes to select and since fast discover period 537 // is over, we probably won't find more in the near future so select more 538 // known entries if possible 539 for pool.knownSelected < targetServerCount { 540 entry := pool.knownSelect.choose() 541 if entry == nil { 542 break 543 } 544 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 545 } 546 } 547 } 548 549 // dial initiates a new connection 550 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 551 if pool.server == nil || entry.state != psNotConnected { 552 return 553 } 554 entry.state = psDialed 555 entry.knownSelected = knownSelected 556 if knownSelected { 557 pool.knownSelected++ 558 } else { 559 pool.newSelected++ 560 } 561 addr := entry.addrSelect.choose().(*poolEntryAddress) 562 log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) 563 entry.dialed = addr 564 go func() { 565 pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) 566 select { 567 case <-pool.quit: 568 case <-time.After(dialTimeout): 569 select { 570 case <-pool.quit: 571 case pool.timeout <- entry: 572 } 573 } 574 }() 575 } 576 577 // checkDialTimeout checks if the node is still in dialed state and if so, resets it 578 // and adjusts connection statistics accordingly. 579 func (pool *serverPool) checkDialTimeout(entry *poolEntry) { 580 if entry.state != psDialed { 581 return 582 } 583 log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey()) 584 entry.state = psNotConnected 585 if entry.knownSelected { 586 pool.knownSelected-- 587 } else { 588 pool.newSelected-- 589 } 590 entry.connectStats.add(0, 1) 591 entry.dialed.fails++ 592 pool.setRetryDial(entry) 593 } 594 595 const ( 596 psNotConnected = iota 597 psDialed 598 psConnected 599 psRegistered 600 ) 601 602 // poolEntry represents a server node and stores its current state and statistics. 603 type poolEntry struct { 604 peer *peer 605 id discover.NodeID 606 addr map[string]*poolEntryAddress 607 lastConnected, dialed *poolEntryAddress 608 addrSelect weightedRandomSelect 609 610 lastDiscovered mclock.AbsTime 611 known, knownSelected bool 612 connectStats, delayStats poolStats 613 responseStats, timeoutStats poolStats 614 state int 615 regTime mclock.AbsTime 616 queueIdx int 617 removed bool 618 619 delayedRetry bool 620 shortRetry int 621 } 622 623 func (e *poolEntry) EncodeRLP(w io.Writer) error { 624 return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) 625 } 626 627 func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 628 var entry struct { 629 ID discover.NodeID 630 IP net.IP 631 Port uint16 632 Fails uint 633 CStat, DStat, RStat, TStat poolStats 634 } 635 if err := s.Decode(&entry); err != nil { 636 return err 637 } 638 addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} 639 e.id = entry.ID 640 e.addr = make(map[string]*poolEntryAddress) 641 e.addr[addr.strKey()] = addr 642 e.addrSelect = *newWeightedRandomSelect() 643 e.addrSelect.update(addr) 644 e.lastConnected = addr 645 e.connectStats = entry.CStat 646 e.delayStats = entry.DStat 647 e.responseStats = entry.RStat 648 e.timeoutStats = entry.TStat 649 e.shortRetry = shortRetryCnt 650 e.known = true 651 return nil 652 } 653 654 // discoveredEntry implements wrsItem 655 type discoveredEntry poolEntry 656 657 // Weight calculates random selection weight for newly discovered entries 658 func (e *discoveredEntry) Weight() int64 { 659 if e.state != psNotConnected || e.delayedRetry { 660 return 0 661 } 662 t := time.Duration(mclock.Now() - e.lastDiscovered) 663 if t <= discoverExpireStart { 664 return 1000000000 665 } 666 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 667 } 668 669 // knownEntry implements wrsItem 670 type knownEntry poolEntry 671 672 // Weight calculates random selection weight for known entries 673 func (e *knownEntry) Weight() int64 { 674 if e.state != psNotConnected || !e.known || e.delayedRetry { 675 return 0 676 } 677 return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow(1-e.timeoutStats.recentAvg(), timeoutPow)) 678 } 679 680 // poolEntryAddress is a separate object because currently it is necessary to remember 681 // multiple potential network addresses for a pool entry. This will be removed after 682 // the final implementation of v5 discovery which will retrieve signed and serial 683 // numbered advertisements, making it clear which IP/port is the latest one. 684 type poolEntryAddress struct { 685 ip net.IP 686 port uint16 687 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 688 fails uint // connection failures since last successful connection (persistent) 689 } 690 691 func (a *poolEntryAddress) Weight() int64 { 692 t := time.Duration(mclock.Now() - a.lastSeen) 693 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 694 } 695 696 func (a *poolEntryAddress) strKey() string { 697 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 698 } 699 700 // poolStats implement statistics for a certain quantity with a long term average 701 // and a short term value which is adjusted exponentially with a factor of 702 // pstatRecentAdjust with each update and also returned exponentially to the 703 // average with the time constant pstatReturnToMeanTC 704 type poolStats struct { 705 sum, weight, avg, recent float64 706 lastRecalc mclock.AbsTime 707 } 708 709 // init initializes stats with a long term sum/update count pair retrieved from the database 710 func (s *poolStats) init(sum, weight float64) { 711 s.sum = sum 712 s.weight = weight 713 var avg float64 714 if weight > 0 { 715 avg = s.sum / weight 716 } 717 s.avg = avg 718 s.recent = avg 719 s.lastRecalc = mclock.Now() 720 } 721 722 // recalc recalculates recent value return-to-mean and long term average 723 func (s *poolStats) recalc() { 724 now := mclock.Now() 725 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 726 if s.sum == 0 { 727 s.avg = 0 728 } else { 729 if s.sum > s.weight*1e30 { 730 s.avg = 1e30 731 } else { 732 s.avg = s.sum / s.weight 733 } 734 } 735 s.lastRecalc = now 736 } 737 738 // add updates the stats with a new value 739 func (s *poolStats) add(value, weight float64) { 740 s.weight += weight 741 s.sum += value * weight 742 s.recalc() 743 } 744 745 // recentAvg returns the short-term adjusted average 746 func (s *poolStats) recentAvg() float64 { 747 s.recalc() 748 return s.recent 749 } 750 751 func (s *poolStats) EncodeRLP(w io.Writer) error { 752 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 753 } 754 755 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 756 var stats struct { 757 SumUint, WeightUint uint64 758 } 759 if err := st.Decode(&stats); err != nil { 760 return err 761 } 762 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 763 return nil 764 } 765 766 // poolEntryQueue keeps track of its least recently accessed entries and removes 767 // them when the number of entries reaches the limit 768 type poolEntryQueue struct { 769 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 770 newPtr, oldPtr, maxCnt int 771 removeFromPool func(*poolEntry) 772 } 773 774 // newPoolEntryQueue returns a new poolEntryQueue 775 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 776 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 777 } 778 779 // fetchOldest returns and removes the least recently accessed entry 780 func (q *poolEntryQueue) fetchOldest() *poolEntry { 781 if len(q.queue) == 0 { 782 return nil 783 } 784 for { 785 if e := q.queue[q.oldPtr]; e != nil { 786 delete(q.queue, q.oldPtr) 787 q.oldPtr++ 788 return e 789 } 790 q.oldPtr++ 791 } 792 } 793 794 // remove removes an entry from the queue 795 func (q *poolEntryQueue) remove(entry *poolEntry) { 796 if q.queue[entry.queueIdx] == entry { 797 delete(q.queue, entry.queueIdx) 798 } 799 } 800 801 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 802 // needs to be removed and removes it from the parent pool too with a callback function. 803 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 804 if q.queue[entry.queueIdx] == entry { 805 delete(q.queue, entry.queueIdx) 806 } else { 807 if len(q.queue) == q.maxCnt { 808 e := q.fetchOldest() 809 q.remove(e) 810 q.removeFromPool(e) 811 } 812 } 813 entry.queueIdx = q.newPtr 814 q.queue[entry.queueIdx] = entry 815 q.newPtr++ 816 }