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