github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/p2p/pex/pex_reactor.go (about) 1 package pex 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/badrootd/nibiru-cometbft/libs/cmap" 10 cmtmath "github.com/badrootd/nibiru-cometbft/libs/math" 11 cmtrand "github.com/badrootd/nibiru-cometbft/libs/rand" 12 "github.com/badrootd/nibiru-cometbft/libs/service" 13 "github.com/badrootd/nibiru-cometbft/p2p" 14 "github.com/badrootd/nibiru-cometbft/p2p/conn" 15 cmtp2p "github.com/badrootd/nibiru-cometbft/proto/tendermint/p2p" 16 ) 17 18 type Peer = p2p.Peer 19 20 const ( 21 // PexChannel is a channel for PEX messages 22 PexChannel = byte(0x00) 23 24 // over-estimate of max NetAddress size 25 // hexID (40) + IP (16) + Port (2) + Name (100) ... 26 // NOTE: dont use massive DNS name .. 27 maxAddressSize = 256 28 29 // NOTE: amplificaiton factor! 30 // small request results in up to maxMsgSize response 31 maxMsgSize = maxAddressSize * maxGetSelection 32 33 // ensure we have enough peers 34 defaultEnsurePeersPeriod = 30 * time.Second 35 36 // Seed/Crawler constants 37 38 // minTimeBetweenCrawls is a minimum time between attempts to crawl a peer. 39 minTimeBetweenCrawls = 2 * time.Minute 40 41 // check some peers every this 42 crawlPeerPeriod = 30 * time.Second 43 44 maxAttemptsToDial = 16 // ~ 35h in total (last attempt - 18h) 45 46 // if node connects to seed, it does not have any trusted peers. 47 // Especially in the beginning, node should have more trusted peers than 48 // untrusted. 49 biasToSelectNewPeers = 30 // 70 to select good peers 50 51 // if a peer is marked bad, it will be banned for at least this time period 52 defaultBanTime = 24 * time.Hour 53 ) 54 55 type errMaxAttemptsToDial struct { 56 } 57 58 func (e errMaxAttemptsToDial) Error() string { 59 return fmt.Sprintf("reached max attempts %d to dial", maxAttemptsToDial) 60 } 61 62 type errTooEarlyToDial struct { 63 backoffDuration time.Duration 64 lastDialed time.Time 65 } 66 67 func (e errTooEarlyToDial) Error() string { 68 return fmt.Sprintf( 69 "too early to dial (backoff duration: %d, last dialed: %v, time since: %v)", 70 e.backoffDuration, e.lastDialed, time.Since(e.lastDialed)) 71 } 72 73 // Reactor handles PEX (peer exchange) and ensures that an 74 // adequate number of peers are connected to the switch. 75 // 76 // It uses `AddrBook` (address book) to store `NetAddress`es of the peers. 77 // 78 // ## Preventing abuse 79 // 80 // Only accept pexAddrsMsg from peers we sent a corresponding pexRequestMsg too. 81 // Only accept one pexRequestMsg every ~defaultEnsurePeersPeriod. 82 type Reactor struct { 83 p2p.BaseReactor 84 85 book AddrBook 86 config *ReactorConfig 87 ensurePeersPeriod time.Duration // TODO: should go in the config 88 89 // maps to prevent abuse 90 requestsSent *cmap.CMap // ID->struct{}: unanswered send requests 91 lastReceivedRequests *cmap.CMap // ID->time.Time: last time peer requested from us 92 93 seedAddrs []*p2p.NetAddress 94 95 attemptsToDial sync.Map // address (string) -> {number of attempts (int), last time dialed (time.Time)} 96 97 // seed/crawled mode fields 98 crawlPeerInfos map[p2p.ID]crawlPeerInfo 99 } 100 101 func (r *Reactor) minReceiveRequestInterval() time.Duration { 102 // NOTE: must be less than ensurePeersPeriod, otherwise we'll request 103 // peers too quickly from others and they'll think we're bad! 104 return r.ensurePeersPeriod / 3 105 } 106 107 // ReactorConfig holds reactor specific configuration data. 108 type ReactorConfig struct { 109 // Seed/Crawler mode 110 SeedMode bool 111 112 // We want seeds to only advertise good peers. Therefore they should wait at 113 // least as long as we expect it to take for a peer to become good before 114 // disconnecting. 115 SeedDisconnectWaitPeriod time.Duration 116 117 // Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) 118 PersistentPeersMaxDialPeriod time.Duration 119 120 // Seeds is a list of addresses reactor may use 121 // if it can't connect to peers in the addrbook. 122 Seeds []string 123 } 124 125 type _attemptsToDial struct { 126 number int 127 lastDialed time.Time 128 } 129 130 // NewReactor creates new PEX reactor. 131 func NewReactor(b AddrBook, config *ReactorConfig) *Reactor { 132 r := &Reactor{ 133 book: b, 134 config: config, 135 ensurePeersPeriod: defaultEnsurePeersPeriod, 136 requestsSent: cmap.NewCMap(), 137 lastReceivedRequests: cmap.NewCMap(), 138 crawlPeerInfos: make(map[p2p.ID]crawlPeerInfo), 139 } 140 r.BaseReactor = *p2p.NewBaseReactor("PEX", r) 141 return r 142 } 143 144 // OnStart implements BaseService 145 func (r *Reactor) OnStart() error { 146 err := r.book.Start() 147 if err != nil && err != service.ErrAlreadyStarted { 148 return err 149 } 150 151 numOnline, seedAddrs, err := r.checkSeeds() 152 if err != nil { 153 return err 154 } else if numOnline == 0 && r.book.Empty() { 155 return errors.New("address book is empty and couldn't resolve any seed nodes") 156 } 157 158 r.seedAddrs = seedAddrs 159 160 // Check if this node should run 161 // in seed/crawler mode 162 if r.config.SeedMode { 163 go r.crawlPeersRoutine() 164 } else { 165 go r.ensurePeersRoutine() 166 } 167 return nil 168 } 169 170 // OnStop implements BaseService 171 func (r *Reactor) OnStop() { 172 if err := r.book.Stop(); err != nil { 173 r.Logger.Error("Error stopping address book", "err", err) 174 } 175 } 176 177 // GetChannels implements Reactor 178 func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { 179 return []*conn.ChannelDescriptor{ 180 { 181 ID: PexChannel, 182 Priority: 1, 183 SendQueueCapacity: 10, 184 RecvMessageCapacity: maxMsgSize, 185 MessageType: &cmtp2p.Message{}, 186 }, 187 } 188 } 189 190 // AddPeer implements Reactor by adding peer to the address book (if inbound) 191 // or by requesting more addresses (if outbound). 192 func (r *Reactor) AddPeer(p Peer) { 193 if p.IsOutbound() { 194 // For outbound peers, the address is already in the books - 195 // either via DialPeersAsync or r.Receive. 196 // Ask it for more peers if we need. 197 if r.book.NeedMoreAddrs() { 198 r.RequestAddrs(p) 199 } 200 } else { 201 // inbound peer is its own source 202 addr, err := p.NodeInfo().NetAddress() 203 if err != nil { 204 r.Logger.Error("Failed to get peer NetAddress", "err", err, "peer", p) 205 return 206 } 207 208 // Make it explicit that addr and src are the same for an inbound peer. 209 src := addr 210 211 // add to book. dont RequestAddrs right away because 212 // we don't trust inbound as much - let ensurePeersRoutine handle it. 213 err = r.book.AddAddress(addr, src) 214 r.logErrAddrBook(err) 215 } 216 } 217 218 // RemovePeer implements Reactor by resetting peer's requests info. 219 func (r *Reactor) RemovePeer(p Peer, reason interface{}) { 220 id := string(p.ID()) 221 r.requestsSent.Delete(id) 222 r.lastReceivedRequests.Delete(id) 223 } 224 225 func (r *Reactor) logErrAddrBook(err error) { 226 if err != nil { 227 switch err.(type) { 228 case ErrAddrBookNilAddr: 229 r.Logger.Error("Failed to add new address", "err", err) 230 default: 231 // non-routable, self, full book, private, etc. 232 r.Logger.Debug("Failed to add new address", "err", err) 233 } 234 } 235 } 236 237 // Receive implements Reactor by handling incoming PEX messages. 238 func (r *Reactor) ReceiveEnvelope(e p2p.Envelope) { 239 r.Logger.Debug("Received message", "src", e.Src, "chId", e.ChannelID, "msg", e.Message) 240 241 switch msg := e.Message.(type) { 242 case *cmtp2p.PexRequest: 243 244 // NOTE: this is a prime candidate for amplification attacks, 245 // so it's important we 246 // 1) restrict how frequently peers can request 247 // 2) limit the output size 248 249 // If we're a seed and this is an inbound peer, 250 // respond once and disconnect. 251 if r.config.SeedMode && !e.Src.IsOutbound() { 252 id := string(e.Src.ID()) 253 v := r.lastReceivedRequests.Get(id) 254 if v != nil { 255 // FlushStop/StopPeer are already 256 // running in a go-routine. 257 return 258 } 259 r.lastReceivedRequests.Set(id, time.Now()) 260 261 // Send addrs and disconnect 262 r.SendAddrs(e.Src, r.book.GetSelectionWithBias(biasToSelectNewPeers)) 263 go func() { 264 // In a go-routine so it doesn't block .Receive. 265 e.Src.FlushStop() 266 r.Switch.StopPeerGracefully(e.Src) 267 }() 268 269 } else { 270 // Check we're not receiving requests too frequently. 271 if err := r.receiveRequest(e.Src); err != nil { 272 r.Switch.StopPeerForError(e.Src, err) 273 r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime) 274 return 275 } 276 r.SendAddrs(e.Src, r.book.GetSelection()) 277 } 278 279 case *cmtp2p.PexAddrs: 280 // If we asked for addresses, add them to the book 281 addrs, err := p2p.NetAddressesFromProto(msg.Addrs) 282 if err != nil { 283 r.Switch.StopPeerForError(e.Src, err) 284 r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime) 285 return 286 } 287 err = r.ReceiveAddrs(addrs, e.Src) 288 if err != nil { 289 r.Switch.StopPeerForError(e.Src, err) 290 if err == ErrUnsolicitedList { 291 r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime) 292 } 293 return 294 } 295 296 default: 297 r.Logger.Error(fmt.Sprintf("Unknown message type %T", msg)) 298 } 299 } 300 301 // enforces a minimum amount of time between requests 302 func (r *Reactor) receiveRequest(src Peer) error { 303 id := string(src.ID()) 304 v := r.lastReceivedRequests.Get(id) 305 if v == nil { 306 // initialize with empty time 307 lastReceived := time.Time{} 308 r.lastReceivedRequests.Set(id, lastReceived) 309 return nil 310 } 311 312 lastReceived := v.(time.Time) 313 if lastReceived.Equal(time.Time{}) { 314 // first time gets a free pass. then we start tracking the time 315 lastReceived = time.Now() 316 r.lastReceivedRequests.Set(id, lastReceived) 317 return nil 318 } 319 320 now := time.Now() 321 minInterval := r.minReceiveRequestInterval() 322 if now.Sub(lastReceived) < minInterval { 323 return fmt.Errorf( 324 "peer (%v) sent next PEX request too soon. lastReceived: %v, now: %v, minInterval: %v. Disconnecting", 325 src.ID(), 326 lastReceived, 327 now, 328 minInterval, 329 ) 330 } 331 r.lastReceivedRequests.Set(id, now) 332 return nil 333 } 334 335 // RequestAddrs asks peer for more addresses if we do not already have a 336 // request out for this peer. 337 func (r *Reactor) RequestAddrs(p Peer) { 338 id := string(p.ID()) 339 if r.requestsSent.Has(id) { 340 return 341 } 342 r.Logger.Debug("Request addrs", "from", p) 343 r.requestsSent.Set(id, struct{}{}) 344 p.SendEnvelope(p2p.Envelope{ 345 ChannelID: PexChannel, 346 Message: &cmtp2p.PexRequest{}, 347 }) 348 } 349 350 // ReceiveAddrs adds the given addrs to the addrbook if theres an open 351 // request for this peer and deletes the open request. 352 // If there's no open request for the src peer, it returns an error. 353 func (r *Reactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { 354 id := string(src.ID()) 355 if !r.requestsSent.Has(id) { 356 return ErrUnsolicitedList 357 } 358 r.requestsSent.Delete(id) 359 360 srcAddr, err := src.NodeInfo().NetAddress() 361 if err != nil { 362 return err 363 } 364 365 srcIsSeed := false 366 for _, seedAddr := range r.seedAddrs { 367 if seedAddr.Equals(srcAddr) { 368 srcIsSeed = true 369 break 370 } 371 } 372 373 for _, netAddr := range addrs { 374 // NOTE: we check netAddr validity and routability in book#AddAddress. 375 err = r.book.AddAddress(netAddr, srcAddr) 376 if err != nil { 377 r.logErrAddrBook(err) 378 // XXX: should we be strict about incoming data and disconnect from a 379 // peer here too? 380 continue 381 } 382 383 // If this address came from a seed node, try to connect to it without 384 // waiting (#2093) 385 if srcIsSeed { 386 go func(addr *p2p.NetAddress) { 387 err := r.dialPeer(addr) 388 if err != nil { 389 switch err.(type) { 390 case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: 391 r.Logger.Debug(err.Error(), "addr", addr) 392 default: 393 r.Logger.Debug(err.Error(), "addr", addr) 394 } 395 } 396 }(netAddr) 397 } 398 } 399 400 return nil 401 } 402 403 // SendAddrs sends addrs to the peer. 404 func (r *Reactor) SendAddrs(p Peer, netAddrs []*p2p.NetAddress) { 405 e := p2p.Envelope{ 406 ChannelID: PexChannel, 407 Message: &cmtp2p.PexAddrs{Addrs: p2p.NetAddressesToProto(netAddrs)}, 408 } 409 p.SendEnvelope(e) 410 } 411 412 // SetEnsurePeersPeriod sets period to ensure peers connected. 413 func (r *Reactor) SetEnsurePeersPeriod(d time.Duration) { 414 r.ensurePeersPeriod = d 415 } 416 417 // Ensures that sufficient peers are connected. (continuous) 418 func (r *Reactor) ensurePeersRoutine() { 419 var ( 420 seed = cmtrand.NewRand() 421 jitter = seed.Int63n(r.ensurePeersPeriod.Nanoseconds()) 422 ) 423 424 // Randomize first round of communication to avoid thundering herd. 425 // If no peers are present directly start connecting so we guarantee swift 426 // setup with the help of configured seeds. 427 if r.nodeHasSomePeersOrDialingAny() { 428 time.Sleep(time.Duration(jitter)) 429 } 430 431 // fire once immediately. 432 // ensures we dial the seeds right away if the book is empty 433 r.ensurePeers() 434 435 // fire periodically 436 ticker := time.NewTicker(r.ensurePeersPeriod) 437 for { 438 select { 439 case <-ticker.C: 440 r.ensurePeers() 441 case <-r.Quit(): 442 ticker.Stop() 443 return 444 } 445 } 446 } 447 448 // ensurePeers ensures that sufficient peers are connected. (once) 449 // 450 // heuristic that we haven't perfected yet, or, perhaps is manually edited by 451 // the node operator. It should not be used to compute what addresses are 452 // already connected or not. 453 func (r *Reactor) ensurePeers() { 454 var ( 455 out, in, dial = r.Switch.NumPeers() 456 numToDial = r.Switch.MaxNumOutboundPeers() - (out + dial) 457 ) 458 r.Logger.Info( 459 "Ensure peers", 460 "numOutPeers", out, 461 "numInPeers", in, 462 "numDialing", dial, 463 "numToDial", numToDial, 464 ) 465 466 if numToDial <= 0 { 467 return 468 } 469 470 // bias to prefer more vetted peers when we have fewer connections. 471 // not perfect, but somewhate ensures that we prioritize connecting to more-vetted 472 // NOTE: range here is [10, 90]. Too high ? 473 newBias := cmtmath.MinInt(out, 8)*10 + 10 474 475 toDial := make(map[p2p.ID]*p2p.NetAddress) 476 // Try maxAttempts times to pick numToDial addresses to dial 477 maxAttempts := numToDial * 3 478 479 for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ { 480 try := r.book.PickAddress(newBias) 481 if try == nil { 482 continue 483 } 484 if _, selected := toDial[try.ID]; selected { 485 continue 486 } 487 if r.Switch.IsDialingOrExistingAddress(try) { 488 continue 489 } 490 // TODO: consider moving some checks from toDial into here 491 // so we don't even consider dialing peers that we want to wait 492 // before dialing again, or have dialed too many times already 493 toDial[try.ID] = try 494 } 495 496 // Dial picked addresses 497 for _, addr := range toDial { 498 go func(addr *p2p.NetAddress) { 499 err := r.dialPeer(addr) 500 if err != nil { 501 switch err.(type) { 502 case errMaxAttemptsToDial, errTooEarlyToDial: 503 r.Logger.Debug(err.Error(), "addr", addr) 504 default: 505 r.Logger.Debug(err.Error(), "addr", addr) 506 } 507 } 508 }(addr) 509 } 510 511 if r.book.NeedMoreAddrs() { 512 // Check if banned nodes can be reinstated 513 r.book.ReinstateBadPeers() 514 } 515 516 if r.book.NeedMoreAddrs() { 517 518 // 1) Pick a random peer and ask for more. 519 peers := r.Switch.Peers().List() 520 peersCount := len(peers) 521 if peersCount > 0 { 522 peer := peers[cmtrand.Int()%peersCount] 523 r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer) 524 r.RequestAddrs(peer) 525 } 526 527 // 2) Dial seeds if we are not dialing anyone. 528 // This is done in addition to asking a peer for addresses to work-around 529 // peers not participating in PEX. 530 if len(toDial) == 0 { 531 r.Logger.Info("No addresses to dial. Falling back to seeds") 532 r.dialSeeds() 533 } 534 } 535 } 536 537 func (r *Reactor) dialAttemptsInfo(addr *p2p.NetAddress) (attempts int, lastDialed time.Time) { 538 _attempts, ok := r.attemptsToDial.Load(addr.DialString()) 539 if !ok { 540 return 541 } 542 atd := _attempts.(_attemptsToDial) 543 return atd.number, atd.lastDialed 544 } 545 546 func (r *Reactor) dialPeer(addr *p2p.NetAddress) error { 547 attempts, lastDialed := r.dialAttemptsInfo(addr) 548 if !r.Switch.IsPeerPersistent(addr) && attempts > maxAttemptsToDial { 549 r.book.MarkBad(addr, defaultBanTime) 550 return errMaxAttemptsToDial{} 551 } 552 553 // exponential backoff if it's not our first attempt to dial given address 554 if attempts > 0 { 555 jitter := time.Duration(cmtrand.Float64() * float64(time.Second)) // 1s == (1e9 ns) 556 backoffDuration := jitter + ((1 << uint(attempts)) * time.Second) 557 backoffDuration = r.maxBackoffDurationForPeer(addr, backoffDuration) 558 sinceLastDialed := time.Since(lastDialed) 559 if sinceLastDialed < backoffDuration { 560 return errTooEarlyToDial{backoffDuration, lastDialed} 561 } 562 } 563 564 err := r.Switch.DialPeerWithAddress(addr) 565 if err != nil { 566 if _, ok := err.(p2p.ErrCurrentlyDialingOrExistingAddress); ok { 567 return err 568 } 569 570 markAddrInBookBasedOnErr(addr, r.book, err) 571 switch err.(type) { 572 case p2p.ErrSwitchAuthenticationFailure: 573 // NOTE: addr is removed from addrbook in markAddrInBookBasedOnErr 574 r.attemptsToDial.Delete(addr.DialString()) 575 default: 576 r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()}) 577 } 578 return fmt.Errorf("dialing failed (attempts: %d): %w", attempts+1, err) 579 } 580 581 // cleanup any history 582 r.attemptsToDial.Delete(addr.DialString()) 583 return nil 584 } 585 586 // maxBackoffDurationForPeer caps the backoff duration for persistent peers. 587 func (r *Reactor) maxBackoffDurationForPeer(addr *p2p.NetAddress, planned time.Duration) time.Duration { 588 if r.config.PersistentPeersMaxDialPeriod > 0 && 589 planned > r.config.PersistentPeersMaxDialPeriod && 590 r.Switch.IsPeerPersistent(addr) { 591 return r.config.PersistentPeersMaxDialPeriod 592 } 593 return planned 594 } 595 596 // checkSeeds checks that addresses are well formed. 597 // Returns number of seeds we can connect to, along with all seeds addrs. 598 // return err if user provided any badly formatted seed addresses. 599 // Doesn't error if the seed node can't be reached. 600 // numOnline returns -1 if no seed nodes were in the initial configuration. 601 func (r *Reactor) checkSeeds() (numOnline int, netAddrs []*p2p.NetAddress, err error) { 602 lSeeds := len(r.config.Seeds) 603 if lSeeds == 0 { 604 return -1, nil, nil 605 } 606 netAddrs, errs := p2p.NewNetAddressStrings(r.config.Seeds) 607 numOnline = lSeeds - len(errs) 608 for _, err := range errs { 609 switch e := err.(type) { 610 case p2p.ErrNetAddressLookup: 611 r.Logger.Error("Connecting to seed failed", "err", e) 612 default: 613 return 0, nil, fmt.Errorf("seed node configuration has error: %w", e) 614 } 615 } 616 return numOnline, netAddrs, nil 617 } 618 619 // randomly dial seeds until we connect to one or exhaust them 620 func (r *Reactor) dialSeeds() { 621 perm := cmtrand.Perm(len(r.seedAddrs)) 622 // perm := r.Switch.rng.Perm(lSeeds) 623 for _, i := range perm { 624 // dial a random seed 625 seedAddr := r.seedAddrs[i] 626 err := r.Switch.DialPeerWithAddress(seedAddr) 627 628 switch err.(type) { 629 case nil, p2p.ErrCurrentlyDialingOrExistingAddress: 630 return 631 } 632 r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) 633 } 634 // do not write error message if there were no seeds specified in config 635 if len(r.seedAddrs) > 0 { 636 r.Switch.Logger.Error("Couldn't connect to any seeds") 637 } 638 } 639 640 // AttemptsToDial returns the number of attempts to dial specific address. It 641 // returns 0 if never attempted or successfully connected. 642 func (r *Reactor) AttemptsToDial(addr *p2p.NetAddress) int { 643 lAttempts, attempted := r.attemptsToDial.Load(addr.DialString()) 644 if attempted { 645 return lAttempts.(_attemptsToDial).number 646 } 647 return 0 648 } 649 650 //---------------------------------------------------------- 651 652 // Explores the network searching for more peers. (continuous) 653 // Seed/Crawler Mode causes this node to quickly disconnect 654 // from peers, except other seed nodes. 655 func (r *Reactor) crawlPeersRoutine() { 656 // If we have any seed nodes, consult them first 657 if len(r.seedAddrs) > 0 { 658 r.dialSeeds() 659 } else { 660 // Do an initial crawl 661 r.crawlPeers(r.book.GetSelection()) 662 } 663 664 // Fire periodically 665 ticker := time.NewTicker(crawlPeerPeriod) 666 667 for { 668 select { 669 case <-ticker.C: 670 r.attemptDisconnects() 671 r.crawlPeers(r.book.GetSelection()) 672 r.cleanupCrawlPeerInfos() 673 case <-r.Quit(): 674 return 675 } 676 } 677 } 678 679 // nodeHasSomePeersOrDialingAny returns true if the node is connected to some 680 // peers or dialing them currently. 681 func (r *Reactor) nodeHasSomePeersOrDialingAny() bool { 682 out, in, dial := r.Switch.NumPeers() 683 return out+in+dial > 0 684 } 685 686 // crawlPeerInfo handles temporary data needed for the network crawling 687 // performed during seed/crawler mode. 688 type crawlPeerInfo struct { 689 Addr *p2p.NetAddress `json:"addr"` 690 // The last time we crawled the peer or attempted to do so. 691 LastCrawled time.Time `json:"last_crawled"` 692 } 693 694 // crawlPeers will crawl the network looking for new peer addresses. 695 func (r *Reactor) crawlPeers(addrs []*p2p.NetAddress) { 696 now := time.Now() 697 698 for _, addr := range addrs { 699 peerInfo, ok := r.crawlPeerInfos[addr.ID] 700 701 // Do not attempt to connect with peers we recently crawled. 702 if ok && now.Sub(peerInfo.LastCrawled) < minTimeBetweenCrawls { 703 continue 704 } 705 706 // Record crawling attempt. 707 r.crawlPeerInfos[addr.ID] = crawlPeerInfo{ 708 Addr: addr, 709 LastCrawled: now, 710 } 711 712 err := r.dialPeer(addr) 713 if err != nil { 714 switch err.(type) { 715 case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress: 716 r.Logger.Debug(err.Error(), "addr", addr) 717 default: 718 r.Logger.Debug(err.Error(), "addr", addr) 719 } 720 continue 721 } 722 723 peer := r.Switch.Peers().Get(addr.ID) 724 if peer != nil { 725 r.RequestAddrs(peer) 726 } 727 } 728 } 729 730 func (r *Reactor) cleanupCrawlPeerInfos() { 731 for id, info := range r.crawlPeerInfos { 732 // If we did not crawl a peer for 24 hours, it means the peer was removed 733 // from the addrbook => remove 734 // 735 // 10000 addresses / maxGetSelection = 40 cycles to get all addresses in 736 // the ideal case, 737 // 40 * crawlPeerPeriod ~ 20 minutes 738 if time.Since(info.LastCrawled) > 24*time.Hour { 739 delete(r.crawlPeerInfos, id) 740 } 741 } 742 } 743 744 // attemptDisconnects checks if we've been with each peer long enough to disconnect 745 func (r *Reactor) attemptDisconnects() { 746 for _, peer := range r.Switch.Peers().List() { 747 if peer.Status().Duration < r.config.SeedDisconnectWaitPeriod { 748 continue 749 } 750 if peer.IsPersistent() { 751 continue 752 } 753 r.Switch.StopPeerGracefully(peer) 754 } 755 } 756 757 func markAddrInBookBasedOnErr(addr *p2p.NetAddress, book AddrBook, err error) { 758 // TODO: detect more "bad peer" scenarios 759 switch err.(type) { 760 case p2p.ErrSwitchAuthenticationFailure: 761 book.MarkBad(addr, defaultBanTime) 762 default: 763 book.MarkAttempt(addr) 764 } 765 }