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