github.com/digdeepmining/go-atheios@v1.5.13-0.20180902133602-d5687a2e6f43/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 "io" 22 "math" 23 "math/rand" 24 "net" 25 "strconv" 26 "sync" 27 "time" 28 29 "github.com/atheioschain/go-atheios/common/mclock" 30 "github.com/atheioschain/go-atheios/ethdb" 31 "github.com/atheioschain/go-atheios/logger" 32 "github.com/atheioschain/go-atheios/logger/glog" 33 "github.com/atheioschain/go-atheios/p2p" 34 "github.com/atheioschain/go-atheios/p2p/discover" 35 "github.com/atheioschain/go-atheios/p2p/discv5" 36 "github.com/atheioschain/go-atheios/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 glog.V(logger.Debug).Infof("connecting to %v, state: %v", p.id, 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 glog.V(logger.Debug).Infof("registered %v", entry.id.String()) 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 glog.V(logger.Debug).Infof("disconnected %v", entry.id.String()) 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 glog.V(logger.Debug).Infof("discovered %v", id.String()) 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 glog.V(logger.Debug).Infof("node list decode error: %v", err) 463 return 464 } 465 for _, e := range list { 466 glog.V(logger.Debug).Infof("loaded server stats %016x fails: %v connStats: %v / %v delayStats: %v / %v responseStats: %v / %v timeoutStats: %v / %v", e.id[0:8], e.lastConnected.fails, e.connectStats.avg, e.connectStats.weight, time.Duration(e.delayStats.avg), e.delayStats.weight, time.Duration(e.responseStats.avg), e.responseStats.weight, e.timeoutStats.avg, e.timeoutStats.weight) 467 pool.entries[e.id] = e 468 pool.knownQueue.setLatest(e) 469 pool.knownSelect.update((*knownEntry)(e)) 470 } 471 } 472 473 // saveNodes saves known nodes and their statistics into the database. Nodes are 474 // ordered from least to most recently connected. 475 func (pool *serverPool) saveNodes() { 476 list := make([]*poolEntry, len(pool.knownQueue.queue)) 477 for i := range list { 478 list[i] = pool.knownQueue.fetchOldest() 479 } 480 enc, err := rlp.EncodeToBytes(list) 481 if err == nil { 482 pool.db.Put(pool.dbKey, enc) 483 } 484 } 485 486 // removeEntry removes a pool entry when the entry count limit is reached. 487 // Note that it is called by the new/known queues from which the entry has already 488 // been removed so removing it from the queues is not necessary. 489 func (pool *serverPool) removeEntry(entry *poolEntry) { 490 pool.newSelect.remove((*discoveredEntry)(entry)) 491 pool.knownSelect.remove((*knownEntry)(entry)) 492 entry.removed = true 493 delete(pool.entries, entry.id) 494 } 495 496 // setRetryDial starts the timer which will enable dialing a certain node again 497 func (pool *serverPool) setRetryDial(entry *poolEntry) { 498 delay := longRetryDelay 499 if entry.shortRetry > 0 { 500 entry.shortRetry-- 501 delay = shortRetryDelay 502 } 503 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 504 entry.delayedRetry = true 505 go func() { 506 select { 507 case <-pool.quit: 508 case <-time.After(delay): 509 select { 510 case <-pool.quit: 511 case pool.enableRetry <- entry: 512 } 513 } 514 }() 515 } 516 517 // updateCheckDial is called when an entry can potentially be dialed again. It updates 518 // its selection weights and checks if new dials can/should be made. 519 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 520 pool.newSelect.update((*discoveredEntry)(entry)) 521 pool.knownSelect.update((*knownEntry)(entry)) 522 pool.checkDial() 523 } 524 525 // checkDial checks if new dials can/should be made. It tries to select servers both 526 // based on good statistics and recent discovery. 527 func (pool *serverPool) checkDial() { 528 fillWithKnownSelects := !pool.fastDiscover 529 for pool.knownSelected < targetKnownSelect { 530 entry := pool.knownSelect.choose() 531 if entry == nil { 532 fillWithKnownSelects = false 533 break 534 } 535 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 536 } 537 for pool.knownSelected+pool.newSelected < targetServerCount { 538 entry := pool.newSelect.choose() 539 if entry == nil { 540 break 541 } 542 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 543 } 544 if fillWithKnownSelects { 545 // no more newly discovered nodes to select and since fast discover period 546 // is over, we probably won't find more in the near future so select more 547 // known entries if possible 548 for pool.knownSelected < targetServerCount { 549 entry := pool.knownSelect.choose() 550 if entry == nil { 551 break 552 } 553 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 554 } 555 } 556 } 557 558 // dial initiates a new connection 559 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 560 if entry.state != psNotConnected { 561 return 562 } 563 entry.state = psDialed 564 entry.knownSelected = knownSelected 565 if knownSelected { 566 pool.knownSelected++ 567 } else { 568 pool.newSelected++ 569 } 570 addr := entry.addrSelect.choose().(*poolEntryAddress) 571 glog.V(logger.Debug).Infof("dialing %v out of %v, known: %v", entry.id.String()+"@"+addr.strKey(), len(entry.addr), knownSelected) 572 entry.dialed = addr 573 go func() { 574 pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) 575 select { 576 case <-pool.quit: 577 case <-time.After(dialTimeout): 578 select { 579 case <-pool.quit: 580 case pool.timeout <- entry: 581 } 582 } 583 }() 584 } 585 586 // checkDialTimeout checks if the node is still in dialed state and if so, resets it 587 // and adjusts connection statistics accordingly. 588 func (pool *serverPool) checkDialTimeout(entry *poolEntry) { 589 if entry.state != psDialed { 590 return 591 } 592 glog.V(logger.Debug).Infof("timeout %v", entry.id.String()+"@"+entry.dialed.strKey()) 593 entry.state = psNotConnected 594 if entry.knownSelected { 595 pool.knownSelected-- 596 } else { 597 pool.newSelected-- 598 } 599 entry.connectStats.add(0, 1) 600 entry.dialed.fails++ 601 pool.setRetryDial(entry) 602 } 603 604 const ( 605 psNotConnected = iota 606 psDialed 607 psConnected 608 psRegistered 609 ) 610 611 // poolEntry represents a server node and stores its current state and statistics. 612 type poolEntry struct { 613 peer *peer 614 id discover.NodeID 615 addr map[string]*poolEntryAddress 616 lastConnected, dialed *poolEntryAddress 617 addrSelect weightedRandomSelect 618 619 lastDiscovered mclock.AbsTime 620 known, knownSelected bool 621 connectStats, delayStats poolStats 622 responseStats, timeoutStats poolStats 623 state int 624 regTime mclock.AbsTime 625 queueIdx int 626 removed bool 627 628 delayedRetry bool 629 shortRetry int 630 } 631 632 func (e *poolEntry) EncodeRLP(w io.Writer) error { 633 return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) 634 } 635 636 func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 637 var entry struct { 638 ID discover.NodeID 639 IP net.IP 640 Port uint16 641 Fails uint 642 CStat, DStat, RStat, TStat poolStats 643 } 644 if err := s.Decode(&entry); err != nil { 645 return err 646 } 647 addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} 648 e.id = entry.ID 649 e.addr = make(map[string]*poolEntryAddress) 650 e.addr[addr.strKey()] = addr 651 e.addrSelect = *newWeightedRandomSelect() 652 e.addrSelect.update(addr) 653 e.lastConnected = addr 654 e.connectStats = entry.CStat 655 e.delayStats = entry.DStat 656 e.responseStats = entry.RStat 657 e.timeoutStats = entry.TStat 658 e.shortRetry = shortRetryCnt 659 e.known = true 660 return nil 661 } 662 663 // discoveredEntry implements wrsItem 664 type discoveredEntry poolEntry 665 666 // Weight calculates random selection weight for newly discovered entries 667 func (e *discoveredEntry) Weight() int64 { 668 if e.state != psNotConnected || e.delayedRetry { 669 return 0 670 } 671 t := time.Duration(mclock.Now() - e.lastDiscovered) 672 if t <= discoverExpireStart { 673 return 1000000000 674 } else { 675 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 676 } 677 } 678 679 // knownEntry implements wrsItem 680 type knownEntry poolEntry 681 682 // Weight calculates random selection weight for known entries 683 func (e *knownEntry) Weight() int64 { 684 if e.state != psNotConnected || !e.known || e.delayedRetry { 685 return 0 686 } 687 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)) 688 } 689 690 // poolEntryAddress is a separate object because currently it is necessary to remember 691 // multiple potential network addresses for a pool entry. This will be removed after 692 // the final implementation of v5 discovery which will retrieve signed and serial 693 // numbered advertisements, making it clear which IP/port is the latest one. 694 type poolEntryAddress struct { 695 ip net.IP 696 port uint16 697 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 698 fails uint // connection failures since last successful connection (persistent) 699 } 700 701 func (a *poolEntryAddress) Weight() int64 { 702 t := time.Duration(mclock.Now() - a.lastSeen) 703 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 704 } 705 706 func (a *poolEntryAddress) strKey() string { 707 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 708 } 709 710 // poolStats implement statistics for a certain quantity with a long term average 711 // and a short term value which is adjusted exponentially with a factor of 712 // pstatRecentAdjust with each update and also returned exponentially to the 713 // average with the time constant pstatReturnToMeanTC 714 type poolStats struct { 715 sum, weight, avg, recent float64 716 lastRecalc mclock.AbsTime 717 } 718 719 // init initializes stats with a long term sum/update count pair retrieved from the database 720 func (s *poolStats) init(sum, weight float64) { 721 s.sum = sum 722 s.weight = weight 723 var avg float64 724 if weight > 0 { 725 avg = s.sum / weight 726 } 727 s.avg = avg 728 s.recent = avg 729 s.lastRecalc = mclock.Now() 730 } 731 732 // recalc recalculates recent value return-to-mean and long term average 733 func (s *poolStats) recalc() { 734 now := mclock.Now() 735 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 736 if s.sum == 0 { 737 s.avg = 0 738 } else { 739 if s.sum > s.weight*1e30 { 740 s.avg = 1e30 741 } else { 742 s.avg = s.sum / s.weight 743 } 744 } 745 s.lastRecalc = now 746 } 747 748 // add updates the stats with a new value 749 func (s *poolStats) add(value, weight float64) { 750 s.weight += weight 751 s.sum += value * weight 752 s.recalc() 753 } 754 755 // recentAvg returns the short-term adjusted average 756 func (s *poolStats) recentAvg() float64 { 757 s.recalc() 758 return s.recent 759 } 760 761 func (s *poolStats) EncodeRLP(w io.Writer) error { 762 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 763 } 764 765 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 766 var stats struct { 767 SumUint, WeightUint uint64 768 } 769 if err := st.Decode(&stats); err != nil { 770 return err 771 } 772 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 773 return nil 774 } 775 776 // poolEntryQueue keeps track of its least recently accessed entries and removes 777 // them when the number of entries reaches the limit 778 type poolEntryQueue struct { 779 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 780 newPtr, oldPtr, maxCnt int 781 removeFromPool func(*poolEntry) 782 } 783 784 // newPoolEntryQueue returns a new poolEntryQueue 785 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 786 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 787 } 788 789 // fetchOldest returns and removes the least recently accessed entry 790 func (q *poolEntryQueue) fetchOldest() *poolEntry { 791 if len(q.queue) == 0 { 792 return nil 793 } 794 for { 795 if e := q.queue[q.oldPtr]; e != nil { 796 delete(q.queue, q.oldPtr) 797 q.oldPtr++ 798 return e 799 } 800 q.oldPtr++ 801 } 802 } 803 804 // remove removes an entry from the queue 805 func (q *poolEntryQueue) remove(entry *poolEntry) { 806 if q.queue[entry.queueIdx] == entry { 807 delete(q.queue, entry.queueIdx) 808 } 809 } 810 811 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 812 // needs to be removed and removes it from the parent pool too with a callback function. 813 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 814 if q.queue[entry.queueIdx] == entry { 815 delete(q.queue, entry.queueIdx) 816 } else { 817 if len(q.queue) == q.maxCnt { 818 e := q.fetchOldest() 819 q.remove(e) 820 q.removeFromPool(e) 821 } 822 } 823 entry.queueIdx = q.newPtr 824 q.queue[entry.queueIdx] = entry 825 q.newPtr++ 826 }