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