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