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