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